服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C语言中的rand()和rand_r()详解

C语言中的rand()和rand_r()详解

2022-07-20 12:13高阶近似 C/C++

这篇文章主要为大家介绍了C语言中的rand()和rand_r(),具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

背景

最近在学《并行程序设计导论》这门课,在做使用Pthreads并行化蒙特卡洛法估计 π \pi π的实验时遇到了一个问题,使用多线程反而要比单线程要慢很多,输出如下所示

C语言中的rand()和rand_r()详解

可以看到,使用一个线程时程序运行只需要2.89031秒,但是使用两个线程时运行时间竟然达到了9.14698秒。

最终发现了问题所在:每个线程在执行下面的函数时,生成随机数使用了rand()函数,就是这个函数的使用才导致多线程运行时所需要的时间反而更长

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(&times_in_cycle_mutex);
    total_times_in_cycle += times_in_cycle;
    pthread_mutex_unlock(&times_in_cycle_mutex);
}

rand()和rand_r()的区别

权威的解释请参考Linux的官方文档
这里主要说说我个人的理解。

rand()

对于rand():

srand()和rand()配套一起使用,可以认为是进程只生成了一个随机数生成器,所有的线程共用这个随机数生成器。每调用一次rand(),rand()都会去修改这个随机数生成器的一些参数,比如说当前种子的值。对于单线程,这样是没有问题的,但是对于多线程而言,这显然会导致临界段问题,为了解决该问题,可能会使用互斥量等方法,下面假设多个线程同时使用rand()的时候使用互斥量来解决该临界段问题。如果rand()调用的次数不多,多线程也问题不大,但是对于上述蒙特卡洛法估计 π \pi π的程序,需要调用很多次rand(),这可能会导致每个线程频繁地对临界段进行上锁和解锁,而临界段被上锁后,其他线程无法完成rand()的调用从而被阻塞,这样会导致效率十分低下,因此会出现使用多线程反而程序运行更慢的问题。

srand()和rand()配套一起使用,可以认为是进程只生成了一个随机数生成器,所有的线程共用这个随机数生成器 这点可以用下面的程序进行验证,该程序从命令行获取要生成随机数的数量以及线程的数量,每个线程负责生成 随机数的数量/线程的数量 个随机数,随机数种子设为0.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
 
int generate_rand_count;
int thread_count;
 
// 被线程执行的函数
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;
 
    // 从命令行获取要生成的随机数的数量以及这些随机数由多少个线程来生成
    generate_rand_count = strtol(argv[1], NULL, 10);
    thread_count = strtol(argv[2], NULL, 10);
    all_threads_id = malloc(sizeof(pthread_t) * thread_count);
 
    // 设置随机数种子
    srand(0);
 
    // 创建线程
    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;
}

执行结果如下所示

C语言中的rand()和rand_r()详解

可以看到,无论使用一个线程生成10个随机数,还是说使用两个线程来生成10个随机数,它们生成的这10个随机数是一样的,这也就验证了 所有的线程共用一个随机数生成器 的观点

rand_r()

rand_r()的声明如下所示

?
1
int rand_r(unsigned int *seedp);

每次使用rand_r()的时候需要传给该函数一个随机数种子的指针,为了解决蒙特卡洛方法估计 π \pi π中出现的问题,使用如下的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(&times_in_cycle_mutex);
    total_times_in_cycle += times_in_cycle;
    pthread_mutex_unlock(&times_in_cycle_mutex);
}

每个线程执行函数do_Monte_Carlo_simulation()。

使用上面的代码后,相当于每个线程生成了它自己独有的随机数生成器,这样就不会有临界段问题,从而解决了多线程运行时所需要的时间反而更长这个问题,下面为更改后程序的输出:

C语言中的rand()和rand_r()详解

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!

原文链接:https://blog.csdn.net/weixin_45937291/article/details/121972158

延伸 · 阅读

精彩推荐
  • C/C++C++入门指南之贪吃蛇游戏的实现

    C++入门指南之贪吃蛇游戏的实现

    这篇文章主要给大家介绍了关于C++入门指南之贪吃蛇游戏实现的相关资料,文章通过示例代码介绍的非常详细,可以让大家能短时间内写出一个贪吃蛇,需要的...

    cqu_shuai6462022-01-24
  • C/C++C语言 操作符分类解析与使用

    C语言 操作符分类解析与使用

    C 语言提供了丰富的操作符,有:算术操作符,移位操作符,位操作符,逻辑操作符,逗号表达式。让我们通读本篇来详细了解吧...

    bite_xwg7082022-02-24
  • C/C++C语言实现流星雨效果流程

    C语言实现流星雨效果流程

    C本篇文章带你用C语言去实现漫天流星雨的效果,代码写的很清晰,效果非常棒,另有视频详解整个过程,相信你一定能看懂,感兴趣的童鞋快来看看吧...

    MAX在码字9782022-03-01
  • C/C++一文搞懂Codec2解码组件

    一文搞懂Codec2解码组件

    这篇文章主要介绍了Codec2解码组件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...

    Kayson123456812022-01-04
  • C/C++用Visual Studio2017写C++静态库图文详解

    用Visual Studio2017写C++静态库图文详解

    这篇文章主要介绍了用Visual Studio2017写C++静态库的图文教程,需要的朋友可以参考下...

    jily1612012021-05-08
  • C/C++C语言实现冒泡排序算法

    C语言实现冒泡排序算法

    冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交...

    C语言教程网9392021-02-23
  • C/C++C语言使用回溯法解旅行售货员问题与图的m着色问题

    C语言使用回溯法解旅行售货员问题与图的m着色问题

    回溯法即是在按条件搜索走不通的情况下退回再选择其他路线的方法,这里我们来看C语言使用回溯法解旅行售货员问题与图的m着色问题的方法示例:...

    Hi_Aaron12212021-04-08
  • C/C++从C++单例模式到线程安全详解

    从C++单例模式到线程安全详解

    下面小编就为大家带来一篇从C++单例模式到线程安全详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C++教程网11422021-04-22