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

  • 相关阅读:
    Django extra 和 annotate
    剑指offer——26反转链表
    剑指offer——25链表中环的入口节点
    剑指offer——24链表中倒数第k个结点
    剑指offer——23调整数组顺序使奇数位于偶数前面
    剑指offer——22表示数值的字符串
    剑指offer——21正则表达式匹配
    剑指offer——20删除链表中重复的结点
    剑指offer——19删除链表的节点
    剑指offer——18打印从1到最大的n位数
  • 原文地址:https://www.cnblogs.com/1yzq/p/13121781.html
Copyright © 2011-2022 走看看