C語言中的rand()和rand_r()詳解
背景
最近在學(xué)《并行程序設(shè)計導(dǎo)論》這門課,在做使用Pthreads并行化蒙特卡洛法估計 π \pi π的實驗時遇到了一個問題,使用多線程反而要比單線程要慢很多,輸出如下所示

可以看到,使用一個線程時程序運行只需要2.89031秒,但是使用兩個線程時運行時間竟然達到了9.14698秒。
最終發(fā)現(xiàn)了問題所在:每個線程在執(zhí)行下面的函數(shù)時,生成隨機數(shù)使用了rand()函數(shù),就是這個函數(shù)的使用才導(dǎo)致多線程運行時所需要的時間反而更長
long long total_times_in_cycle;
long long local_number_toss;
pthread_mutex_t times_in_cycle_mutex = PTHREAD_MUTEX_INITIALIZER;
void* do_Monte_Carlo_simulation(void* my_rank){
double offset = RAND_MAX / (double)2;
long long times_in_cycle = 0;
long long i;
for(i = 0; i < local_number_toss; ++i){
double x = rand() / offset - 1;
double y = rand() / offset - 1;
if(x*x + y*y < 1){
times_in_cycle++;
}
}
pthread_mutex_lock(×_in_cycle_mutex);
total_times_in_cycle += times_in_cycle;
pthread_mutex_unlock(×_in_cycle_mutex);
}
rand()和rand_r()的區(qū)別
權(quán)威的解釋請參考Linux的官方文檔
這里主要說說我個人的理解。
rand()
對于rand():
srand()和rand()配套一起使用,可以認為是進程只生成了一個隨機數(shù)生成器,所有的線程共用這個隨機數(shù)生成器。每調(diào)用一次rand(),rand()都會去修改這個隨機數(shù)生成器的一些參數(shù),比如說當(dāng)前種子的值。對于單線程,這樣是沒有問題的,但是對于多線程而言,這顯然會導(dǎo)致臨界段問題,為了解決該問題,可能會使用互斥量等方法,下面假設(shè)多個線程同時使用rand()的時候使用互斥量來解決該臨界段問題。如果rand()調(diào)用的次數(shù)不多,多線程也問題不大,但是對于上述蒙特卡洛法估計 π \pi π的程序,需要調(diào)用很多次rand(),這可能會導(dǎo)致每個線程頻繁地對臨界段進行上鎖和解鎖,而臨界段被上鎖后,其他線程無法完成rand()的調(diào)用從而被阻塞,這樣會導(dǎo)致效率十分低下,因此會出現(xiàn)使用多線程反而程序運行更慢的問題。
srand()和rand()配套一起使用,可以認為是進程只生成了一個隨機數(shù)生成器,所有的線程共用這個隨機數(shù)生成器?這點可以用下面的程序進行驗證,該程序從命令行獲取要生成隨機數(shù)的數(shù)量以及線程的數(shù)量,每個線程負責(zé)生成?隨機數(shù)的數(shù)量/線程的數(shù)量?個隨機數(shù),隨機數(shù)種子設(shè)為0.
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
int generate_rand_count;
int thread_count;
// 被線程執(zhí)行的函數(shù)
void* thread_func(void* my_rank){
int i;
int local_rand_count = generate_rand_count / thread_count;
for(i = 0; i < local_rand_count; ++i){
printf("%d ", rand());
}
}
int main(int argc, char* argv[]){
pthread_t* all_threads_id;
// 從命令行獲取要生成的隨機數(shù)的數(shù)量以及這些隨機數(shù)由多少個線程來生成
generate_rand_count = strtol(argv[1], NULL, 10);
thread_count = strtol(argv[2], NULL, 10);
all_threads_id = malloc(sizeof(pthread_t) * thread_count);
// 設(shè)置隨機數(shù)種子
srand(0);
// 創(chuàng)建線程
int i;
for(i = 0; i < thread_count; ++i){
pthread_create(&all_threads_id[i], NULL, thread_func, (void*) i);
}
for(i = 0; i < thread_count; ++i){
pthread_join(all_threads_id[i], NULL);
}
printf("\n");
free(all_threads_id);
return 0;
}
執(zhí)行結(jié)果如下所示

可以看到,無論使用一個線程生成10個隨機數(shù),還是說使用兩個線程來生成10個隨機數(shù),它們生成的這10個隨機數(shù)是一樣的,這也就驗證了?所有的線程共用一個隨機數(shù)生成器?的觀點
rand_r()
rand_r()的聲明如下所示
int rand_r(unsigned int *seedp);
每次使用rand_r()的時候需要傳給該函數(shù)一個隨機數(shù)種子的指針,為了解決蒙特卡洛方法估計 π \pi π中出現(xiàn)的問題,使用如下的代碼:
long long total_times_in_cycle;
long long local_number_toss;
pthread_mutex_t times_in_cycle_mutex = PTHREAD_MUTEX_INITIALIZER;
void* do_Monte_Carlo_simulation(void* my_rank){
unsigned int local_seed = time(NULL);
double offset = RAND_MAX / (double)2;
long long times_in_cycle = 0;
long long i;
for(i = 0; i < local_number_toss; ++i){
double x = rand_r(&local_seed) / offset - 1;
double y = rand_r(&local_seed) / offset - 1;
if(x*x + y*y < 1){
times_in_cycle++;
}
}
pthread_mutex_lock(×_in_cycle_mutex);
total_times_in_cycle += times_in_cycle;
pthread_mutex_unlock(×_in_cycle_mutex);
}
每個線程執(zhí)行函數(shù)do_Monte_Carlo_simulation()。
使用上面的代碼后,相當(dāng)于每個線程生成了它自己獨有的隨機數(shù)生成器,這樣就不會有臨界段問題,從而解決了多線程運行時所需要的時間反而更長這個問題,下面為更改后程序的輸出:

總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
深入探討Linux靜態(tài)庫與動態(tài)庫的詳解(一看就懂)
本篇文章是對Linux靜態(tài)庫與動態(tài)庫進行了詳細的分析介紹,需要的朋友參考下2013-05-05
C++中關(guān)于Crt的內(nèi)存泄漏檢測的分析介紹
本篇文章介紹了,在C++中關(guān)于Crt的內(nèi)存泄漏檢測的分析說明。需要的朋友參考下2013-04-04
詳解c語言中的 strcpy和strncpy字符串函數(shù)使用
strcpy 和strcnpy函數(shù)是字符串復(fù)制函數(shù)。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數(shù)使用,感興趣的朋友跟隨小編要求看看吧2018-10-10

