php 在 fpm 下生成随机数研究

原创 2015年11月18日 16:05:27

本文发表于 rust.love

下面这段代码,在 fpm 下,交替打开注释 [1] 和 [2] ,你会发现多次请求随机数相同的现象。

echo str_repeat("=", 100), '<br>';
echo getmypid(), '<br>';
echo str_repeat("=", 100), '<br>';
// [1] mt_srand(10);
// [2] mt_srand();
for ($i = 0; $i < 10; $i ++) { */
     echo mt_rand(1000, 9999), '<br>'; */
}

带着这个疑问,我探寻了一下源代码,最后大体上整理了一个源码的流程出来

#define N             MT_N                 /* length of state vector MT_N = 624*/ 
#define M             (397)                /* a period parameter */
#define hiBit(u)      ((u) & 0x80000000U)  /* mask all but highest   bit of u */
#define loBit(u)      ((u) & 0x00000001U)  /* mask all but lowest    bit of u */
#define loBits(u)     ((u) & 0x7FFFFFFFU)  /* mask     the highest   bit of u */
#define mixBits(u, v) (hiBit(u)|loBits(v)) /* move hi bit of u to hi bit of v */

#define twist(m,u,v)  (m ^ (mixBits(u,v)>>1) ^ ((php_uint32)(-(php_int32)(loBit(u))) & 0x9908b0dfU))

PHP_FUNCTION(mt_rand) {
    ......

    if (!BG(mt_rand_is_seeded)) {
        php_mt_srand(GENERATE_SEED() TSRMLS_CC);
    }
    ......
}


PHP_FUNCTION(mt_srand)
{
    long seed = 0;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &seed) == FAILURE)
        return;

    if (ZEND_NUM_ARGS() == 0)
        seed = GENERATE_SEED();

    php_mt_srand(seed TSRMLS_CC);
}


PHPAPI void php_mt_srand(php_uint32 seed TSRMLS_DC)
{
    /* Seed the generator with a simple uint32 */
    php_mt_initialize(seed, BG(state));
    php_mt_reload(TSRMLS_C);

    /* Seed only once */
    BG(mt_rand_is_seeded) = 1;
}

static inline void php_mt_initialize(php_uint32 seed, php_uint32 *state)
{
    /* Initialize generator state with seed
       See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier.
       In previous versions, most significant bits (MSBs) of the seed affect
       only MSBs of the state array.  Modified 9 Jan 2002 by Makoto Matsumoto. */

    register php_uint32 *s = state;
    register php_uint32 *r = state;
    register int i = 1;

    *s++ = seed & 0xffffffffU;
    for( ; i < N; ++i ) {
        *s++ = ( 1812433253U * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffU;
        r++;
    }
}


static inline void php_mt_reload(TSRMLS_D)
{
    /* Generate N new values in state
       Made clearer and faster by Matthew Bellew (matthew.bellew@home.com) */

    register php_uint32 *state = BG(state);
    register php_uint32 *p = state;
    register int i;

    for (i = N - M; i--; ++p)
        *p = twist(p[M], p[0], p[1]);
    for (i = M; --i; ++p)
        *p = twist(p[M-N], p[0], p[1]);
    *p = twist(p[M-N], p[0], state[0]);
    BG(left) = N;
    BG(next) = state;
}

通过代码可以看出, mt_randmt_srand 其实没有明显的差别,无非是 seed 的处理上有一些差别。而存储随机数相关的数据结构在一个叫 _php_basic_globals 的结构体里,代码如下(摘抄了部分):

/* rand.c */
php_uint32   state[MT_N+1];  /* state vector + 1 extra to not violate ANSI C */
php_uint32   *next;       /* next random value is computed from here */
int      left;        /* can *next++ this many times before reloading */

unsigned int rand_seed; /* Seed for rand(), in ts version */

zend_bool rand_is_seeded; /* Whether rand() has been seeded */
zend_bool mt_rand_is_seeded; /* Whether mt_rand() has been seeded */

通过上面的结构体,我们看到了一些状态等信息,上面的这些代码主要展示了生成随机数的一些关键环节。下面我们说一说产生随机数不随机相关情况:

  1. 先全部重启 php-fpm
  2. 第一个实验,在调用 mt_rand(1000, 9999) 之前,调用 mt_srand(10),此时所有请求的随机数相同,这个没有问题。

  3. 去掉 mt_srand(10),如果你fpm fork 了3个进程,那么根据pid查看这三个进程的随机数都是相同的,这三个进程执行一轮以后,就会生成新的随机数。

  4. 把 mt_srand(10) 换成 mt_srand() 恢复了正确的随机数
  5. 再进行第1步操作后,只调用 mt_rand(1000, 9999) 一切正常

那么为什么 mt_srand(seed) 以后,就出现不随机的状况了呢?
因为fork出来的进程,当我们mt_srand(seed) 以后,进程所共享的 seed 是一样的,所以不同进程生成的随机数才是一样的。而 mt_srand() 这种调用,系统是根据GENERATE_SEED()来生成的seed,这个seed是包含了一些pid等其他信息,所以每个进程的seed是不同的,最后生成的随机数也是不同的。
下面是 GENERATE_SEED() 的定义:

#ifdef PHP_WIN32
#define GENERATE_SEED() (((long) (time(0) * GetCurrentProcessId())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))
#else
#define GENERATE_SEED() (((long) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))
#endif

最后的代码是一个简单的扩展函数,输出执行test_mt_rand过程中内核的一些相关数据,php版本5.5.30

PHP_FUNCTION(test_mt_rand)
{
    long min;
    long max;
    long number;
    int  argc = ZEND_NUM_ARGS();

    if (argc != 0) {
        if (zend_parse_parameters(argc TSRMLS_CC, "ll", &min, &max) == FAILURE) {
            return;
        } else if (max < min) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "max(%ld) is smaller than min(%ld)", max, min);
            RETURN_FALSE;
        }
    }

    zval *opt_array;
    MAKE_STD_ZVAL(opt_array);   
    array_init(opt_array);
    add_assoc_long(opt_array, "MIN", min);
    add_assoc_long(opt_array, "MAX", max);
    add_assoc_long(opt_array, "NUMBER0", number);
    add_assoc_long(opt_array, "BG_SEEDED", BG(mt_rand_is_seeded));
    add_assoc_long(opt_array, "GEN_SEED", GENERATE_SEED());

    if (!BG(mt_rand_is_seeded)) {
        php_mt_srand(GENERATE_SEED() TSRMLS_CC);
    }

    /*
     * Melo: hmms.. randomMT() returns 32 random bits...
     * Yet, the previous php_rand only returns 31 at most.
     * So I put a right shift to loose the lsb. It *seems*
     * better than clearing the msb. 
     * Update: 
     * I talked with Cokus via email and it won't ruin the algorithm
     */

    int index = 0;
    php_uint32 *st= BG(state);  

    zval *subArr;
    MAKE_STD_ZVAL(subArr);
    array_init(subArr);
    for (index = 0; index < MT_N; index ++) {
        add_index_long(subArr, index, *st++);
    }
    add_assoc_zval(opt_array, "state1", subArr);

    add_assoc_long(opt_array, "BG(next)", *BG(next));
    number = (long) (php_mt_rand(TSRMLS_C) >> 1);

    php_uint32 *st1 = BG(state);
    zval *subArr1;
    MAKE_STD_ZVAL(subArr1);
    array_init(subArr1);
    for (index = 0; index < MT_N; index ++) {
        add_index_long(subArr1, index, *st1 ++);    
    }
    add_assoc_zval(opt_array, "state2", subArr1);

    add_assoc_long(opt_array, "NUMBER_php_mt_rand", number);
    if (argc == 2) {
        RAND_RANGE(number, min, max, PHP_MT_RAND_MAX);
        add_assoc_long(opt_array, "PHP_MT_RAND_MAX", PHP_MT_RAND_MAX);
        add_assoc_long(opt_array, "RETURN", number);
    }
    add_assoc_long(opt_array, "SEED", BG(rand_seed));
    add_assoc_long(opt_array, "LEFT", BG(left));
    RETURN_ZVAL(opt_array, 1, 0);
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

一个基于QR Code encoder的 php 扩展,更高效的生成二维码

一个基于QR Code encoder的 php 扩展,更高效的生成二维码

php扩展开发笔记(8)继承和实现接口

这里面主要就是实现了一个没有方法的自定义Exception类,并且继承了Exception这个类。采用了zend_register_internal_class_ex 这个带有 _ex 后缀的宏,这个...

Js生成随机数的研究

由js生成一切随机数的基础都是Math.random(),这个方法比较特别,生成的随机数落在的区间是[0,1),进行一次操作的话,js只能生成一个类似于[n,m)这样,左闭右开的区间。所以当有一些特殊...

php-5.2.13-fpm

  • 2010-03-04 11:14
  • 197KB
  • 下载

php-5.2.12-fpm

  • 2010-03-04 10:54
  • 198KB
  • 下载

php 生成不重复随机数(组)的几种方法

下面写几种生成不重复随机数的方法,直接上代码吧 <?php define('RANDOM_MAX', 100); define('COUNT', 10); echo 'max random...

php-5.2.13-fpm-0.5.14.diff.gz

  • 2014-09-22 10:00
  • 197KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)