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

目录
  • 背景
  • rand()和rand_r()的区别
    • rand()
    • rand_r()
  • 总结

背景

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

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

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

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.

#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;
}

执行结果如下所示

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

rand_r()

rand_r()的声明如下所示

int rand_r(unsigned int *seedp);

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

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()。

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

总结

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

(0)

相关推荐

  • 详解C语言中rand函数的使用

    前言 我们在编程实现算法的过程中,往往需要使用到随机数.由于计算机是一台以逻辑为基础的机器,没法做到真正的随机(大概量子计算机可以?).所以计算机生成的是伪随机数,供我们使用. 我们使用C语言的rand函数,生成的也是伪随机数. c语言之rand函数的使用 1.写入头文件 #include <stdlib.h> #include <stdio.h> #include <time.h> 2.变量的定义 void main( void ) { int i,k; 3.sran

  • C语言使用rand函数生成随机数

    目录 rand()函数 函数原型: 初步使用 生成0到99的随机数 再次运行  发现问题 查看rand()函数的官方文档 srand()函数 srand的官方介绍 初步使用srand() 再次运行 发现问题 解决问题:time函数 时间戳 rand()函数 生成随机数使用rand()函数 函数原型: int rand (void); 头文件<stdlib.h> 初步使用  目前该"随机数"貌似成功生成 生成0到99的随机数 再次运行  发现问题 大家有木有发现其实这三次的运

  • Java和c语言随机数Random代码详细

    目录 一.随机数的创建步骤 1.导包 2.创建对象 3.数据接收 二.Java随机数游戏 三. c语言的猜数字 一.随机数的创建步骤 1.导包 所谓导包就是在Java中已经定义好的文件,我们直接引用过来即可 随机数导包: import  java.util.Random; 2.创建对象 其实创建对象就是在开辟内存空间 随机数创建对象: Random d=new Random();  其中d是任意起的变量名,其余形式格式固定 3.数据接收 数据接收就是创建一个变量去接受产生的随机数. 接受随机数:

  • C语言随机数生成教程(rand和srand用法)

    在实际编程中,我们经常需要生成随机数,例如,贪吃蛇游戏中在随机的位置出现食物,扑克牌游戏中随机发牌. 在C语言中,我们一般使用 <stdlib.h> 头文件中的 rand() 函数来生成随机数,它的用法为: int rand (void); void 表示不需要传递参数. C语言中还有一个 random() 函数可以获取随机数,但是 random() 不是标准函数,不能在 VC/VS 等编译器通过,所以比较少用. rand() 会随机生成一个位于 0 ~ RAND_MAX 之间的整数. RAN

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

    目录 背景 rand()和rand_r()的区别 rand() rand_r() 总结 背景 最近在学<并行程序设计导论>这门课,在做使用Pthreads并行化蒙特卡洛法估计 π \pi π的实验时遇到了一个问题,使用多线程反而要比单线程要慢很多,输出如下所示 可以看到,使用一个线程时程序运行只需要2.89031秒,但是使用两个线程时运行时间竟然达到了9.14698秒. 最终发现了问题所在:每个线程在执行下面的函数时,生成随机数使用了rand()函数,就是这个函数的使用才导致多线程运行时所需要

  • C语言中 “_at()” 特殊地址定位详解

    C语言中 "_at()" 特殊地址定位详解 在keil里面,有一个特殊地址定位的指令,就是将一个变量或常量定位到一个指定的地址上面 指令为 __at ,使用方法如下 int variable __at(0x8000) = 100; 意为将variable变量定位到0x8000这个地址. 指令的目的是告诉链接器将指定地址定位到某个地址里面,在使用该功能的时候需要注意一点的是,地址的范围需要符合单片机的datasheet给出的地址映射表, 注意,如果地址超出了flash的最大范围,程序将无

  • C语言中memcpy 函数的用法详解

    C语言中memcpy 函数的用法详解 memcpy(内存拷贝函数) c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中. void* memcpy(void* destination, const void* source, size_t num); void* dest 目标内存 const void* src 源内存 size_t num 字节个数 库中实现的memcpy函数 struct { ch

  • C语言中fgets和fscanf区别详解

    C语言中fgets和fscanf区别详解 一.作用上的大概区别: ①fgets:从文件中读取一行数据存入缓冲区(fgets遇到回车才会结束,不对空格和回车做任何转换就录入到缓冲区,结束后再往缓冲区写多一个\0,所以它是读一行数据) ②fscanf:从文件中读取一段数据存入缓冲区(fscanf遇到空格或回车就结束,它会把空格或回车转换为\0,所以它是读一小段数据) 二.举个例子:把a.txt文件中的内容复制到b.txt. a.txt中的内容(第一行中"我爱你小白"和"开玩笑&q

  • C语言中scanf与scnaf_s函数详解

    目录 scanf_s 使用scanf_s scanf 使用scanf 总结 scanf_s scanf_s()函数是Microsoft公司VS开发工具提供的一个功能相同的安全标准输入函数,从vc++2005开始,VS系统提供了scanf_s().在调用该函数时,可以提供一个数字以表明最多读取多少位字符. MSDN上scanf_s的简单模型:int scanf_s( const char *format [, argument]...);一般我们常用的格式:scanf_s(输入格式,输入流 [,输

  • C语言中static和auto用法详解

    目录 static的第一种用法:定义为静态变量 static的第二种用法:有理说不清,直接代码见真知 auto的用法:直接代码见真知 总结 static的第一种用法:定义为静态变量 何为静态变量?存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化.就是只进行一次初始化.不理解?看代码! 代码见真知: #include<stdio.h> int main() { void fun(); //定义一个空函数 for(int i=0;i<3;i++) { fun();

  • Go语言中interface语法与使用详解

    目录 初识interface 基本语法 其他注意事项 interface底层实现 iface eface 侵入式与非侵入式的理解 interface的应用场景 类型转换 实现多态功能 补充:interface 与 nil 的比较 总结 初识interface Go语言的面向对象的知识点时,发现它的面向对象能力全靠 interface 撑着,而且它的 interface 还与我们以前知道的 interface 完全不同.故而整个过程不断的思考为什么要如此设计?这样设计给我们带来了什么影响? int

  • C语言中的sscanf()函数使用详解

    sscanf() - 从一个字符串中读进与指定格式相符的数据. 函数原型: Int sscanf( string str, string fmt, mixed var1, mixed var2 ... ); int scanf( const char *format [,argument]... ); 说明: sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源. 其中的format可以是一个或多个 {%[*] [width] [{h |

  • C语言中free函数的使用详解

    free函数是我们再写C语言程序时常用的函数,但是使用时需要注意,一不小心很肯能会引起吐核. 注意:free函数与malloc()函数配对使用,释放malloc函数申请的动态内存.对于free(p)这句语句,如果p 是NULL 指针,那么free 对p 无论操作多少次都不会出问题.如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误. 看一个程序 #include <stdio.h> #include <stdlib.h> int main() { cha

  • sql语言中delete删除命令语句详解

    Table:用于标示删除的数据表的名称. 1:该表必须是一个事实存在的表,对于该处的表必须是一个实际存在于数据库中的表格,必能是由于select等语句创造出来的中间表. 2:该方式可以通过联合的方式同时在两个表格中满足一定关联条件的数据. Top(*)用于指定删除的数据的数量 1:Top(N)表示在该表中删除表格中最靠前的N条数据. 2:在delete中不能和order连用,所以也不能通过top和order by连用来删除按照某一条件排序的全部的靠前的几条记录. 3:改语句不能用于sql2000

随机推荐