当前位置:首页 > 编程开发

多线程中生成随机数序列重复问题的解决方法

webgou17年前 (2009-11-14)编程开发298

 

使用过随机数的程序员都知道在程序中并不能够实现的真正的完全的随机数函数。随机数函数产生的是通过公式计算出来的一系列伪随机数,这个公式会采用一个种子数计算出一个数,而该数将成为产生下一个数的种子数。基于产生随机数的原理,两次调用随机数后产生的随机数序列将是一样的,显然,这不是我们的期望的结果。

为了解决上述问题,使得随机数函数产生的数尽量随机,编程语言通常提供了设定种子数的功能,而一般情况下,程序员会使用当前时间作为种子数,这样两次调用随机函数产生的随机数序列就会不同,进而达到更加随机的效果。

对于C/C++而言,通常产生的随机数的方法是调用以下两个函数:

 

srand(time(NULL));      // 设定随机数种子, 参数内是使用当前时间作为种子

rand();                 // 产生一个随机数

 

由于一般情况下调用srand(time(NULL))的时间不会一样,所以每次产生的随机数序列也会不同,因而可以达到近似完全随机的效果。但是,如果是在多线程的环境下需要使用随机数,情况将变得有所不同。

以下是一个在线程中生成随机数的示例,为了能将生成的随机数显示出来,测试程序肺采用了MFC对话框,并将窗口句柄传入到了线程中,当线程中产生了一个随机数后,以发消息的形式通知对话框显示产生出来的随机数。

 

/* 产生随机数的线程  */

UINT RandThread(LPVOID p)

{

     if(NULL == p)

         return 1;

 

     PTHREADPARA pt = (PTHREADPARA)p;

 

     // 初始化随机种子

     ::srand(time(NULL));

 

     // 生成随机数,并通过消息显示在界面上

     int nRnd;

     for(int i = 1; i <= 20; i++)

     {

         nRnd = ::rand();

         PostMessage(pt->hWind, WM_USER_RAND, pt->nThreadFlag, nRnd);

     }

 

     // 释放线程参数空间

     if(pt)

     {

         delete pt;

         pt = NULL;

     }

 

     return 0;

}

 

在测试程序中,将上述线程启动5次,可以发现,得到的随机数序列是完全一样的。下图是其中一次运行的结果:

 

 线程生成相同的随机数序列

分析其原因,可能是因为5个线程启动的时间相差无几,而不同线程调用rand()函数时相对是独立的,因而产生的随机数序列也就是相同的。

实际上time(NULL)所取得的时间只能精确到秒,而启动5个线程如果在同一秒内完成(经测试,在1.6G单核机器上测试时一秒内启动10个线程也没有问题,能在同一秒内启动的线程数量与机器当前繁忙程度、CUP性能等有很大关系),就必然形成上述生成同一随机数序列的现象。

通过上述分析,如果能令不同线程设定的随机数种子不同,就应该可以令不同线程产生不同的随机数序列。一个容易想到的办法是在相隔较长的时间里启动不同的线程,但是如果要求所有线程在较短的时间内连续启动呢?那我们就只能寻找可以设定不同种子数的办法。

既然time(NULL)的精度只能到秒级,那么我们可以尝试使用时间精度更高的函数来获得一个种子数。

配合使用结构体timeb与函数ftime可以取得毫秒级的时间,它应当可以使得多线程生成同一序列随机数的现状有所改观。修改初始化种子数的代码如下:

 

struct timeb stb;

ftime(&stb);

srand((unsigned)stb.millitm);)

 

重新运行测试程序,可以发现以毫秒级的时间作为种子数依然感觉不够用,部分线程产生的随机数序列依然相同,也就是说在同一毫秒内也可以启动两个以下的线程。那么我们只能继续寻找精度更高的与时间相关的函数。

经查找资料,函数QueryPerformanceCounter可以返回高精度的计数器值,其精度可达微秒级。通常该函数会与函数QueryPerformanceFrequency配合使用,函数QueryPerformanceFrequency的功能是如果当前机器存在定时器则查询出当前机器定时器的频率,我们可以利用QueryPerformanceFrequency测试当前系统里是否有这个高精度的定时器,如果有,则可以调用QueryPerformanceCounter获得一个精度很高的计数值。

利用QueryPerformanceFrequency和QueryPerformanceCounter设定种子数的代码如下:

 

if(::QueryPerformanceFrequency(&nFrequency))

     {

         LARGE_INTEGER nStartCounter;  

         ::QueryPerformanceCounter(&nStartCounter);

         ::srand((unsigned)nStartCounter.LowPart);

}

 

再次运行测试程序,可以发现,每个线程产生的随机数序列已经完全不一样了。下图是其中一次运行的结果:

 

 线程生成不同的随机数序列

以下列出完整的线程代码,供参考。

 

/* 初始化随机种子 */

void InitRand()

{

     // 如果支持高性能精度计数器,则使用其初始化随机种子(微秒级)

     LARGE_INTEGER nFrequency;  

     if(::QueryPerformanceFrequency(&nFrequency))

     {

         LARGE_INTEGER nStartCounter;  

         ::QueryPerformanceCounter(&nStartCounter);

         ::srand((unsigned)nStartCounter.LowPart);

     }

     else // 否则使用当前系统时间初始化随机种子(毫秒级)

     {

         struct timeb stb;

         ::ftime(&stb);

         ::srand((unsigned)stb.millitm);

     }

}

 

/* 产生随机数的线程  */

UINT RandThread(LPVOID p)

{

     if(NULL == p)

         return 1;

 

     PTHREADPARA pt = (PTHREADPARA)p;

 

     // 初始化随机种子

     //::srand(time(NULL));

     InitRand();

 

     // 生成随机数,并通过消息显示在界面上

     int nRnd;

     for(int i = 1; i <= 20; i++)

     {

         nRnd = ::rand();

         PostMessage(pt->hWind, WM_USER_RAND, pt->nThreadFlag, nRnd);

     }

 

     // 释放线程参数空间

     if(pt)

     {

         delete pt;

         pt = NULL;

     }

 

     return 0;

}

 

 

本文来自:http://blog.csdn.net/ishallwin/archive/2009/09/25/4593500.aspx

扫描二维码推送至手机访问。

版权声明:本文由知了博客发布,如需转载请注明出处。

本文链接:https://www.webgou.info/?id=86

分享给朋友:

“多线程中生成随机数序列重复问题的解决方法” 的相关文章

VPN(虚拟专用网络)

VPN(虚拟专用网络)…

WM_DESTROY与WM_CLOSE

由于wce中,要重写wtl的CFrameWindow,然后采用CWindowImpl去实现View,view里面加入了函数: 并加入CWindowImpl实现的CButton成员函数: CMYButton m_btn1; ...…

Android拍照实现方式概述

编程人员在Android手机操作系统中可以实现许多功能来满足各种用户不同的需求。比如我们以前介绍的通话监控功能等。在这里就先来为大家详细介绍一下有关Android拍照的实现方法。Android拍照有两种方式,一是使用android.hardware.Camera,二是使用 Intent("…

mysql修改字段

修改     ALTER   TABLE   TableName   CHANGE   Field_name_tobe_change   new_Name &n…

Android消息传递应用功能解析

Android手机操作系统中有一种叫做Intent的消息传递机制。这在实际编程中是一个核心技术,值得我们去深入的研究。在这里大家将会了解到有关Android消息传递的一些基础应用技巧,帮助大家理解。每一个Cursor、ContentResolver做为一个小的注册中心,相关观察者可以在这个中心注册,…

CentOS7安装Docker与使用篇

 一、在CentOS7上安装docker篇1. 查看系统版本: ...…

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。