zoukankan      html  css  js  c++  java
  • 多线程安全的随机数生产函数

     
    (一)遇到的问题
    软件中写了这么一个函数,用来生成有数字和字母组成的随机的指定字符个数的字符串。运行的时候发现多线程调用这个函数,返回的两个字符串一模一样。这是不符合预期的,会导致程序出现bug.
    原因很简单,多个线程如果同时调用到这个函数,那么time(NULL)返回的种子是一模一样的,所以rand函数返回的序列在两个线程中也是一模一样的。所有返回的字符串也是一模一样的。
    这个是伪随机数的性质:相同的种子返回相同的随机数序列。

     string GenerateRandomString(int nLen)
     {
      std::string strRet;
      const char* cArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      int nArrayLen = strlen(cArray);
      //设置当前时间为种子
      srand(::time(NULL));
      for (int i = 0; i < nLen; i++)
      {
       int nIndex = rand() % nArrayLen;
       strRet += cArray[nIndex];
      }
      return strRet;
     }

    (二)解决方案

    1.使用GUID来生产随机字符串,保证多线程下是安全的。这种方法和函数的本意虽然有些偏离,但是在软件中这个确实是解决了问题。那如果一定要保持函数原型不变,需要生成有数字和字母组成的随机的指定字符个数的字符串,该怎么办呢?第二种方法可以解决。

    string GenerateRandomString()

    {

              GUID guid;
              ::CoInitialize(NULL);
              CoCreateGuid(&guid);
              CHAR buff[65] = {0};
              StringCchPrintfA(
                  buff, 64, ("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"),
                  guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1],
                  guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
                  guid.Data4[6], guid.Data4[7]);
              return string(buff);
    }

    2.使用rand_s代替rand函数,这个函数是不依赖种子的更安全的伪随机数生成器。并且是线程安全的。

    string GenerateRandomKey(int nLen)
    {
     std::string strRet;
     const char* cArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
     int nArrayLen = strlen(cArray);
     for (int i = 0; i < nLen; i++)
     {
      unsigned uRandomValue = 0;
      rand_s(&uRandomValue);
      int nIndex = uRandomValue % nArrayLen;
      strRet += cArray[nIndex];
     }
     return strRet;
    }

    (三)rand以及rand_s函数的实现(vs2017)

    1.rand函数实现

    // Seeds the random number generator with the provided integer.
    extern "C" void __cdecl srand(unsigned int const seed)
    {
        __acrt_getptd()->_rand_state = seed;
    }
     
    // Returns a pseudorandom number in the range [0,32767].
    extern "C" int __cdecl rand()
    {
        __acrt_ptd* const ptd = __acrt_getptd();
        ptd->_rand_state = ptd->_rand_state * 214013 + 2531011;
        return (ptd->_rand_state >> 16) & RAND_MAX;
    }
     

    可以看出,srand函数设置的种子保存在线程本地存储(TLS)中的_rand_state中,而rand函数就是对这个值进行了简单的数学运算然后又赋值给了_rand_state。可以看出这种方式生成的随机数是确定的,也就是说相同的种子返回相同的随机数序列。

    从这里也看到了随机数的种子是和线程相关的,每个线程是隔离的,多线程代码执行确实是安全的。但是问题是种子如果一样了生成的随机数就一样了。

    顺便提下_rand_state的默认值是1

    2.rand_s函数实现

    extern "C" errno_t __cdecl rand_s(unsigned int* const result)
    {
        _VALIDATE_RETURN_ERRCODE(result != nullptr, EINVAL);
        *result = 0;
        if (!__acrt_RtlGenRandom(result, static_cast<ULONG>(sizeof(*result))))
        {
            errno = ENOMEM;
            return errno;
        }
        return 0;
    }
    函数只是简单调用了下RtlGenRandom函数而已。
     
    Generates a pseudorandom number. This is a more secure version of the function rand
    也就是说他是一个安全版本的伪随机数。(还是伪随机数哦)
    为什么更安全,我没怎么明白原理。总体来说就是不能再依赖种子了。所以更安全了 

    对比java来说:

    rand相当于Random类

    rand_s相当于SecureRandom

     
    java随机数参考了这两篇文章:
    https://blog.csdn.net/zy_281870667/article/details/88579442
    里头的FIPS 140-2页面已经没有了,在这个文章中:https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules

    (四)真随机数和伪随机数

    先分享一个真随机数的网站: https://www.random.org/

    真正的随机数是使用物理现象产生的:比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等等,这样的随机数发生器叫做物理性随机数发生器,它们的缺点是技术要求比较高。

    随机函数是按照一定算法模拟产生的,其结果是确定的,是可见的。我们可以这样认为这个可预见的结果其出现的概率是100%。所以用计算机随机函数所产生的“随机数”并不随机,是伪随机数。

    https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator

    这篇文章中讲到了真随机数,不过我个人的理解是秘密学安全的随机数应该都是属于真随机数。

    所以,rand是伪随机数,rand_s是真随机数。

    (五) c++11生产随机数的方法

    可以参考下面这些文章:

     https://docs.microsoft.com/en-us/cpp/standard-library/random?view=vs-2019

    https://docs.microsoft.com/en-us/cpp/standard-library/random-device-class?view=vs-2019

    总结来说

    random_device是真随机数

    mt19937是伪随机数。

    两者的区别:

    There are two highly useful URNGs in Visual Studio—mt19937 and random_device—as shown in this comparison table:

     使用示例:

    1.random_device

    #include <random>
    #include <iostream>
    using namespace std;

    int main()
    {
        random_device gen;

        cout << "entropy == " << gen.entropy() << endl;
        cout << "min == " << gen.min() << endl;
        cout << "max == " << gen.max() << endl;

        cout << "a random value == " << gen() << endl;
        cout << "a random value == " << gen() << endl;
        cout << "a random value == " << gen() << endl;
    }

    2.mt19937

    #include <random>
    #include <iostream>
    using namespace std;

    int main()
    {
        mt19937 gen(time(NULL));

        cout << "entropy == " << gen.entropy() << endl;
        cout << "min == " << gen.min() << endl;
        cout << "max == " << gen.max() << endl;

        cout << "a random value == " << gen() << endl;
        cout << "a random value == " << gen() << endl;
        cout << "a random value == " << gen() << endl;
    }

    (六)各大开源软件如何使用随机数

    1.chromiuim

     
    源代码位置:srcase and_util_win.cc
    namespace base {
    void RandBytes(void* output, size_t output_length) {
      char* output_ptr = static_cast<char*>(output);
      while (output_length > 0) {
        const ULONG output_bytes_this_pass = static_cast<ULONG>(std::min(
            output_length, static_cast<size_t>(std::numeric_limits<ULONG>::max())));
        const bool success =
            RtlGenRandom(output_ptr, output_bytes_this_pass) != FALSE;
        CHECK(success);
        output_length -= output_bytes_this_pass;
        output_ptr += output_bytes_this_pass;
      }
    }
    }  // namespace base
    可以看出,chromium在windows平台上也直接使用了RtlGenRandom函数。
     
     

    2.Qt

    https://doc.qt.io/qt-5/qrandomgenerator.html#securelySeeded

    https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qrandom.cpp?h=dev

    Qt中提供真随机数是使用rand_s作为fallback机制(兜底),真随机数依赖cpu信息,没有深入研究了。

    3.mozilla

    也是从看Qt代码才在注释中发现mozilla代码的。

    https://hg.mozilla.org/mozilla-central/file/722fdbff1efc/security/nss/lib/freebl/win_rand.c#l146

    直接使用了RtlGenRandom作为随机数的实现。

    (七)参考资料

    https://www.random.org/

    https://blog.csdn.net/czc1997/article/details/78167705

    https://blog.csdn.net/hahachenchen789/article/details/84251345

    https://www.iteye.com/blog/kevin-hust-744284

    https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful

  • 相关阅读:
    Swift3 重写一个带占位符的textView
    Swift3 使用系统UIAlertView方法做吐司效果
    Swift3 页面顶部实现拉伸效果代码
    Swift3 倒计时按钮扩展
    iOS 获取当前对象所在的VC
    SpringBoot在IDEA下使用JPA
    hibernate 异常a different object with the same identifier value was already associated with the session
    SpringCloud IDEA 教学 番外篇 后台运行Eureka服务注册中心
    SpringCloud IDEA 教学 (五) 断路器控制台(HystrixDashboard)
    SpringCloud IDEA 教学 (四) 断路器(Hystrix)
  • 原文地址:https://www.cnblogs.com/1yzq/p/13121781.html
Copyright © 2011-2022 走看看