Почему pcntl_fork () копирует объекты PHP?

В руководстве для pcntl_fork () говорится:

Функция pcntl_fork () создает дочерний процесс, который отличается от родительского процесса только своими PID и PPID.

Однако запуск этого простого теста меня удивил:

class Foo
{
    public function bar()
    {
        if (pcntl_fork()) {
            echo spl_object_hash($this), PHP_EOL;
        } else {
            echo spl_object_hash($this), PHP_EOL;
        }
    }
}

(new Foo)->bar();

Результат выглядит так:

000000005ec7fd31000000003f0fcfe6
000000006b4cd5fc000000007fee8ab7

Из того, что говорится в документации, я ожидал, что родитель и потомок будут использовать одни и те же переменные, и, в частности, когда fork () ed изнутри объекта, я ожидал, что ссылка на объект будет одинаковой в обоих процессы. Но приведенный выше пример показывает, что это не так.

Интересно отметить, что здесь не происходит клонирования, похоже, что объект просто скопирован. Если я добавлю __clone() функцию, я вижу, что она не вызывается во время разветвления.

Есть ли причина, по которой переменные / объекты не используются обоими процессами, или какое-либо хорошее чтение по предмету?


person BenMorel    schedule 02.04.2013    source источник
comment
Итак, справочная страница fork2 (), которую рекомендуется прочитать в соответствии с документами PHP , входит несколько более подробных сведений, в том числе Все виртуальное адресное пространство родителя реплицируется в дочернем элементе, как вы уже заметили, репликация в дочернем элементе НЕ совпадает с общим с дочерним. PHP не построен с приложениями, которым это нужно, и, хотя вы можете создавать свои собственные сложные механизмы, что-то вроде Java или, возможно, асинхронного node.js кажется вам более подходящим.   -  person Wrikken    schedule 02.04.2013
comment
Я думаю, вы правы, это объяснение похоже на ответ @hek2mgl, и я был введен в заблуждение тем фактом, что ресурсы (которые, очевидно, не могут быть скопированы) являются общими, я предположил, что все переменные и т. Д. Также были общими.   -  person BenMorel    schedule 02.04.2013
comment
Зависит от ресурса, который я считаю: открытые дескрипторы файлов: да, но не все «ресурсы» будут либо совместно использоваться, либо скопированы, это зависит от их реализации в источнике php.   -  person Wrikken    schedule 02.04.2013
comment
Спасибо за точность!   -  person BenMorel    schedule 02.04.2013


Ответы (2)


Хэш объекта не будет вычисляться при создании объекта (как можно было бы подумать). Хэш объекта будет вычислен при первом вызове spl_object_hash() для объекта. Это после вилки в вашем примере.

Также обратите внимание, что для вычисления хеша используется некоторая случайность, следовательно, разные хеши.

person hek2mgl    schedule 02.04.2013
comment
Но если бы объекты были одинаковыми, неважно, когда вызывается spl_object_hash (), верно? Хеши должны быть идентичными, насколько я понимаю. - person BenMorel; 02.04.2013
comment
Обратите внимание, что вилка создает два отдельных процесса после вилки, все переменные и память дублируются. - person hek2mgl; 02.04.2013
comment
@LtWorf Взгляните на исходный код php. Файл: ext / spl / php_spl.c Строка: 722 - person hek2mgl; 02.04.2013
comment
Если вы вызвали spl_object_hash перед форком, случайное число, которое он использует и генерирует при первом вызове, будет фиксированным и одинаковым для обоих процессов, а выходные данные для двух объектов будут одинаковыми. Но даже в этом случае нет совместного использования, два процесса имеют полностью отдельные области памяти. - person andyhassall; 02.04.2013
comment
@Benjamin Я думаю, вы неправильно понимаете fork(). Fork полностью дублирует текущий процесс (на уровне ядра), дублирует всю память, среду и ресурсы, которыми владеет процесс разветвления, и что важно: указатель инструкции на CPU. После этого два отдельных процесса запускаются из одного источника. Больше ничем не делятся. После того, как вы это поймете, вы узнаете, что __clone() не по теме в этом вопросе - person hek2mgl; 02.04.2013
comment
Я провел несколько дополнительных тестов, которые, кажется, подтверждают то, что вы говорите. Это очень сбивает с толку, когда spl_object_hash () вызывается перед fork () ing, хеш-код одинаков как для родительского, так и для дочернего, даже если на самом деле это два разных объекта (один и тот же хеш, но в двух разных адресных пространствах). . - person BenMorel; 02.04.2013
comment
@ hek2mgl Да, меня сбили с толку несколько руководств, в которых содержится предостережение о том, что ресурсы совместно используются обоими процессами, что может быть опасно. Так поправьте меня, если я ошибаюсь, только ресурсы являются общими, все переменные, объекты и т. Д. Копируются, а родительский и дочерний не разделяют никаких ссылок, кроме этих ресурсов? - person BenMorel; 02.04.2013
comment
Позвольте мне попытаться объяснить дальше. Самым наивным исполнением процесса, которого можно было ожидать, является exec. Используя exec, процесс будет загружен в память, и указатель инструкции будет установлен на первую инструкцию. Затем процесс начинается. Но с fork все иначе. Процесс не будет загружен в память, вместо этого будет дублироваться память другого уже запущенного процесса. Также будет продублирован указатель инструкции процесса. Это делается на уровне ядра. Теперь у вас есть два полностью независимых процесса, которые будут продолжать работать в одной точке. - person hek2mgl; 02.04.2013
comment
Итак, все пространство памяти процесса php дублируется (что объясняет, почему переменные, объекты и т. Д. Не являются общими), оставляя только ресурсы, открытые до fork (), совместно используемые родительским и дочерним? - person BenMorel; 02.04.2013
comment
@ Бенджамин. Нет, после этого момента они не разделяют переменные и память. Но будьте осторожны с внешними ресурсами. Если вы, например, откроете файл перед разветвлением, оба процесса будут писать в один и тот же файл (конечно). Но если один процесс закроет файл, он все равно будет открыт для другого. Обратите внимание, что это не то же самое для TCP-соединений. Например, TCP-соединение с mysql. Если один процесс закрывает соединение, другой процесс больше не сможет использовать соединение. - person hek2mgl; 02.04.2013
comment
@ hek2mgl Большое спасибо за ваше объяснение, теперь оно намного яснее! - person BenMorel; 02.04.2013

Ссылка на объект такая же в разветвленном процессе, потому что расположение в памяти объекта в пространстве памяти дочернего процесса такое же.

Хэш вычисляется как адрес объекта XOR случайная маска (которая генерируется только один раз), как вы можете прочитать в исходном коде PHP, ext/spl/php_spl.c:

PHPAPI void php_spl_object_hash(zval *obj, char *result TSRMLS_DC) /* {{{*/
{
    intptr_t hash_handle, hash_handlers;
    char *hex;

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

        SPL_G(hash_mask_handle)   = (intptr_t)(php_mt_rand(TSRMLS_C) >> 1);
        SPL_G(hash_mask_handlers) = (intptr_t)(php_mt_rand(TSRMLS_C) >> 1);
        SPL_G(hash_mask_init) = 1; 
    }    

    hash_handle   = SPL_G(hash_mask_handle)^(intptr_t)Z_OBJ_HANDLE_P(obj);
    hash_handlers = SPL_G(hash_mask_handlers)^(intptr_t)Z_OBJ_HT_P(obj);

    spprintf(&hex, 32, "%016x%016x", hash_handle, hash_handlers);

    strlcpy(result, hex, 33); 
    efree(hex);
}
/* }}} */

Если генератор случайных чисел был засеян до вызова функции, вы получите точно такой же результат как для дочернего, так и для родительского процесса. Но в данном случае это не так, и каждый процесс вычисляет собственное начальное число. Код для 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

Как видите, начальное значение зависит от идентификатора процесса, который, конечно же, отличается для родительского и дочернего.

Итак, разные начальные числа генератора случайных чисел, разные случайные маски, разные хеши.

person Joni    schedule 02.04.2013
comment
Спасибо за дополнительную информацию. Теперь я понял, почему хеши разные. Однако похоже, что вы ошибаетесь относительно двух адресных пространств, которые различаются: если я разветвляю объект, заставляю родителя назначать переменную и читать ее от дочернего элемента, я не получаю это значение. Или, может быть, я неправильно понял вашу точку зрения? - person BenMorel; 02.04.2013
comment
Я хочу сказать, что адресное пространство дочернего элемента является точной копией адресного пространства родительского объекта: все объекты находятся по одним и тем же адресам. В противном случае fork() либо сломает указатели, либо придется их переписать. - person Joni; 02.04.2013