作者:张裕鹏 日期:2021-6-26

在 C 语言中,我们一般使用 头文件中 rand() 函数来生成随机数。但是存在一个问题,那就是每次生成的随机数都一样:

  1. int main()
  2. {
  3. printf("rand [%d]\n", rand());
  4. return 0;
  5. }

结果:
image.png

随机数的本质

多次运行上面的代码,会发现每次产生的随机数都一样,这是怎么回事呢?为什么随机数并不随机呢?
实际上,rand() 函数产生的随机数是伪随机数,是根据一个数值按照某个公式推算出来的,这个数值我们称之为“种子”。种子和随机数之间的关系是一种正态分布,如下图所示:

C 语言连续生成多个随机数 - 图2

种子在每次启动计算机时是随机的,但是一旦计算机启动以后它就不再变化了;也就是说,每次启动计算机以后,种子就是定值了,所以根据公式推算出来的结果(也就是生成的随机数)就是固定的。

重新播种

我们可以通过 srand() 函数来重新“播种”,这样种子就会发生改变。srand() 的用法为:

  1. void srand (unsigned int seed);

它需要一个 unsigned int 类型的参数。在实际开发中,我们可以用时间作为参数,只要每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同。

获取系统时间

这边穿插讲一下,如何获取系统的时间,通常情况下,如果只是想获取秒级的时间单位可以使用 Linux 系统下的 time() 函数,具体的使用可以参考:Linux time()函数解析

生成连续随机数

有了时间函数作为种子,那就可以生成连续的随机数了。
注意,如果需要每次生成的随机数都不相同,那一定要有不同的种子(不同的时间)
但是我们在开发的时候通常会遗忘这个情况,比如下面的代码(我们通过在代码中连续两次调用 srand(time(0)) 来模仿这种情形)

  1. int main()
  2. {
  3. srand(time(0));
  4. for(int i=0; i<10; i++){
  5. printf("[%d]\n",rand());
  6. }
  7. printf("--------------\n");
  8. srand(time(0));
  9. for(int i=0; i<10; i++){
  10. printf("[%d]\n",rand());
  11. }
  12. return 0;
  13. }

运行结果:
image.png
从结果可以看出来,两次的得到随机数是相同的。主要是因为,整个程序运行非常快,在不到一秒的时间内就跑完了,所以两次的 time(0) 获取到的时间是一样的,因此随机数也就相同。

如果代码这样修改,就可以避免这种问题

int main()
{
    srand(time(0));
    for(int i=0; i<10; i++){    
        printf("[%d]\n",rand());
    }
    sleep(1);

    printf("--------------\n");
    srand(time(0));
    for(int i=0; i<10; i++){    
        printf("[%d]\n",rand());
    }
    return 0;
}

运行结果:
image.png
通过一个睡眠时间可以确保两次获取的时间不一致,这样就能生成不会重复的随机数了。

但是又可能会有人说,每次运行这个函数都睡眠 1 秒,那效率也太低了。有没有更好的办法呢?
解决办法其实也很简单,将种子的时间换成毫秒时间,然后睡眠时间改成 1 毫秒,那就 ok 了。如果有更苛刻的要求,甚至可以试试微秒。但是一般情况下,毫秒就足以满足条件。下面来介绍一下,如果获取 Linux 系统下的毫秒时间。

获取毫秒时间

unsigned long get_time_ms()
{
    struct timespec time1 = {0, 0};
    clock_gettime(CLOCK_REALTIME, &time1);
    return 1000*time1.tv_sec + time1.tv_nsec/1000000;
}

关于 clock_gettime() 函数的使用,可以参考:linux 下的clock_gettime() 获取时间函数

int main()
{
    srand(get_time_ms());
    for(int i=0; i<10; i++){    
        printf("[%d]\n",rand());
    }
    msleep(1);

    printf("--------------\n");
    srand(get_time_ms());
    for(int i=0; i<10; i++){    
        printf("[%d]\n",rand());
    }
    return 0;
}

运行结果:
image.png
这样就可以最大限度的避免,由于睡眠导致的系统性能异常的问题了。至于上述代码中用到的 msleep() 函数,这是自己实现的微秒睡眠函数,其代码可以参考我的另一篇文章:Linux 下常见的休眠函数