Pull to refresh

Comments 47

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

Поэтому если база нужна и в родителе и в потомках — нельзя допускать чтобы соединение было открыто на момент вызова pcntl_fork().
Быть может я не совсем понимаю, но вроде же есть pconnect, который живет дольше (по крайней мере в частном, но самом частом случае СУБД — mysql).
Я честно признаюсь, что не до конца знаю внутренности работы с pconnect, но у меня не заработало ни так ни так. Первый запрос делается, второй возвращает ошибку. Как мне кажется, проблема в том, что у коннекта есть некий уникальный идентификатор, и даже если коннект остается открытым на уровне расширения — экземпляр обертки в PHP уничтожается, тем самым отвязывая идентификатор от коннекта. При повторной попытке обратиться с тем же идентификатором начинается печалька.
pconnect связан с транзакциями как опера с балетом
Вы, наверно, что-то перепутали, я ничего не говорил о транзакциях. Речь идет о подключении к базе и его области и времени жизни.
Вот именно, что нельзя юзать родительский коннект, и вы решили не побороть проблему, а обойти ее путем перекладывания всей работы с базой в родителя

>> родительский процесс должен заниматься базой и дочерние процессы делать «черную работу» и возвращать результат

т.о. получаются следующие ограничения
во-первых, потомки не могут полноценно работать с базой (транзакции и все такое)
во-вторых, и это очень важно, перекладывая работу с базой на один поток, мы лишаемся всех преимуществ многопоточной работы, ведь согласитесь, 16 потоков на 16 процах писали бы в базу гораздо быстрее чем один
в-третьих, когда все потомки отработали и передали данные в родителя для записи в базу, ему нужно все эти данные записать по-очереди, и пока он пишет, он не может запустить еще потомков, ну либо тут нужно организовывать какую-то асинхронную очередь для их обработки

в общем сплошные минусы, а решение то на самом деле элементарное — сразу после форка сбрасывать коннект и соединяться заново, а родителю перед его запросами проверять — не разорван ли его коннект и пересоединяться если нужно
Мне кажется вы хотели ответить Автору. Лично я вообще работаю с базой только из потомков, поскольку зачастую обращение к удаленному серверу БД является наибольшей задержкой, и распараллеливание запросов к разным серверам по форкам дает существенный выигрыш.
нельзя всем потокам использовать одно подключение к базе, потому что когда будет race condition по использованию одного подключения из 50 потоков база скажет «простите, но я пошла нахрен» и просто закроет подключение. уже сталкивался с таким.

лучшим решением тут будет из каждого чайлда создавать собственное подключение к базе, у родителя свое
ну в общем то, о чем я и говорил
В случае данного CliScript коннект с базой данных существовать будет даже после «смерти» детей, т.к. там вызывается
system("kill -9 {$this->pid}");
он не освобождает ресурсы.
Из опыта скажу что mysqli разрывает соединение как у детей так и родителя, нужен реконнект.
(тестировалось только на линукс)

Отчего же на винде не потестили? Note: This extension is not available on Windows platforms. Документация — очень интересная штука, между прочим.
[offtop]Неужели кто то поднимает на винде сервера с php?[/offtop]

Не понятна отсылка к докам.
Автору видимо это не нужно, а кому нужно будет, сами почитают, разве не очевидно?
code.google.com/p/scalr/source/browse/scalr-2/trunk/app/cron-ng/jobs/Poller.php

Посмотрите как работают наши кронджобы. Мы используем Semaphore Functions: очереди, семафоры и шаред мемори. Все настраивается, можно к примеру запустить пулл процессов и он будет висеть и обрабатывать задачи как толкьо они будут появлятся в очереди. Можно просто переодически запускать крон, он стартует воркеры, делает работу и прибивается. И много других вкусных плюшек.
У меня есть некоторые наработки в эту сторону github.com/kulikov/php-threads-manager

там мультипроцессорность реализуется тремя способами (на выбор):
1. через запуск отделных процессов через popen()
2. через tcp сокеты stream_socket_client()
3. через pcntl_fork() // не доделано

В основном я пользовался popen как самым надежным и прозрачным способом. сокеты использовлись коллегами для дебага под виндой. а форки так и не доделал.
Самый главный вопрос: как наиболее оптимально организовать обмен данными между родителем и потомком. По завершению работы в потомке складывать ответ во временный файл, а родитель из него потом все считывает? Пожалуй надо так и сделать. :)
я тоже делал через popen
работает стабильно
Что-то мне кажется, что shm_* (или shmop_* — но их не юзал) должны быть более эффективны, особенно учитывая нативные реализации семафоров и очередей (на которых довольно просто реализуется, например пул соединений с СУБД).
да, согласен. шаред мемори должно быть быстрее. надо попробовать прикрутить.
шаред мемори однозначно быстрее
не делал не говори,
реализовать пул соединений с БД не так уж и просто.
имеется ввиду для параллельного использования.
graber, переделай в своем проекте комменты на английский.
По своему опыту скажу. что очень тяжело использовать чужой код с комментами на японском или испанском.
Всё это проходил и из опыта вот что могу сказать:
Если подключить PECL-расширение proctitle (расширение бета, по причине отсутствия тестов, но работает исправно, проверено) то воркеры смогут задавать свои заголовки в ps или top, например. Это полезно что бы знать кто какие роли выполняет и чем занят.
Fork работает по принципу copy-on-write поэтому самое тяжелое лучше выгрузить в память сразу.
Некоторые расширения ведут себя не корректно после fork-а. Так, например, mysqli разрывает соединение не только в чаилдах, но и в родителе.
Комуникацию по коммандам удобно длеать через сигналы через расширение POSIX. Для более сложной комуникации лучше использовать pair сокеты. По pair советам можно отдавать результат из воркера в мастер по завершению воркера (как-то проще чем через семафоры/мутексы). При включении в дело libevent можно получить очень функциональный событийный демон.
Не забывайте делать setsid (posix_setsid).
Спасибо! Это то, чего мне не хватает как раз. Сходу непонятно про session leader. Если сможете на пальцах объяснить что к чему то спасибо :)
Если вы про SID то setsid запускает новую сессию для вызвавшего setsid процесса причем SID равен PID-у этого процесса (этот процесс называется лидером). Таким образом процесс становится независим от чужой сессии, которая может быть кем-то прибита. Каждый последующий порожденный процесс от лидера или его потомка будет содержатся в этой сессии. В некоторых системах при падении лидера падает и сессия (но жаль что далеко не во всех). По сути, сессия позволяет связать пачку процессов: команада pkill -s SID разошлёт сигнал всем процессам в сессии. ps -Fs SID выведет процессы сессии и т.д.
писали игровых демонов на php. pcntl+libevent. демон висел на порту и слушал и обрабатывал HTTP запросы. был некой прослойкой между фронтом и БД. запускалось форками. управление через SIGNAL. Libevent позволял создавать таймеры и прочую вкусноту. в процессе разработки были исправлены баги в php_libevent с Тони.
Андрей,
просто автор немного не в теме оборотной стороны медали fork()
А сколько ещё надо пофиксить… :)
Понравилось :)

case SIGTERM:
    echo 'opa!';
    return;
Автор забыл указать
что это возможно только в бэдграундовских процессах
«бэдграундовских» — оговорочка по Фрейду :)
а по делу ляпнуть слабо?
Приветствую всех)

При всех моих тёплых чувствах к PHP, он, как мне кажется, не лучший инструмент для реализации многопоточности и мультипроцессности…

Кроме того, как тут уже говорили, при создании многопоточного приложения всегда следует контролировать доступ к общим ресурсам… Для примера в методе log (к которому насколько я понимаю обращаются «рабочие») следовало воспользоваться хотя бы функцией file_put_contents с флагами FILE_APPEND и LOCK_EX.

Ещё бросилось в глаза в методе getFormattedMessage почему-то автор предпочитает вызвать 4 раза функцию str_replace:
$message_formatted = str_replace('%time', $time, self::OUTPUT_FORMAT );
$message_formatted = str_replace('%role', $role, $message_formatted);
$message_formatted = str_replace('%pid', $this->pid, $message_formatted);
$message_formatted = str_replace('%message', $message, $message_formatted);
но ведь можно было использовать массивы в качестве параметров $search и $replace… хотя тут вообще уместна другая функция — sprintf (или её аналог vsprintf)…

Код просматривал мельком, поэтому сказал о том, что бросилось в глаза (getFormattedMessage рядом с методом log =) ).

Многопоточность и мультипроцессность — эта та область где даже «гуру» совершают ошибки… возможно стоит получше изучить сам php, прежде чем хвататься за многопоточность. А если уж берёшься за неё, то лучше хорошенько разобраться во всех механизмах прежде, чем писать на хабр.
Ваша активность на хабре многословна. Сразу видно, что Вы очень хорошо в чем-то разбираетесь и хочется прислушаться к Вашим советам сильно-сильно! Вы случайно не преподаватель программирования?
я не считаю уровень своих знаний достаточным, чтобы писать статьи на хабр.
Может быть удобнее будет запускать процесс с помощью system(«php /path/script.php»);?
Честно говоря, не проверял этот метод, но управляю демонами через PHP
#!/usr/bin/php
<?php
echo "________________________________________\n";
$command = "restart";
if (isset($argv[1]))
    $command = trim($argv[1]);
echo "Начинаем сканировать процессы...\n";
exec("ps -A -F | grep php", $output);
$folder = dirname(__FILE__);
$php = "php";
$names = array(
    "system",
    "inspector",
    "drivers",
    "android",
);
echo "Определяем демоны multitaxi...\n";
foreach ($output as $out) {
    if (preg_match_all("#.*php ".$folder."/(.*)\.php#isU", $out, $matches)) {
        $matches[0][0] = str_replace("  ", " ", $matches[0][0]);
        $matches[0][0] = str_replace("  ", " ", $matches[0][0]);
        $matches[0][0] = str_replace("  ", " ", $matches[0][0]);
        $matches[0][0] = str_replace("  ", " ", $matches[0][0]);
        $matches[0][0] = str_replace("  ", " ", $matches[0][0]);
        $daemon = $matches[1][0];
        $matches = explode(" ", $matches[0][0]);
        $pid = $matches[1];
        //echo $daemon;
        if (in_array($daemon, $names)) {
            if ($command != "view")
                exec("kill -9 ".$pid);
            echo "Процесс: ".$daemon." (".$pid.")\n";
        }
    }
}
if ($command != "stop" && $command != "view") {
    echo "Запускаем процессы...\n";
    foreach ($names as $d) {
        exec($php." ".$folder."/".$d.".php 1>> /dev/null 2>> /dev/null &");
        echo $php." ".$folder."/".$d.".php 1>> /dev/null 2>> /dev/null & \n";
    }
}
echo "Готово!\n";
echo "________________________________________\n";

?>

Код писался на коленке за 5 минут. За
$matches[0][0] = str_replace("  ", " ", $matches[0][0]);
стыдно :)
> exec($php." ".$folder."/".$d.".php 1>> /dev/null 2>> /dev/null &");
данная строка порождает Zombie
Осторожно, не наплодите стадо вампиров
Скрипт и написан для того, что бы не запускать стадо вампиров =) Сначала он ищет все запущенные с именами, заданными в массиве, убивает их, а потом запускает новые
ну сказано же чуть ли не открытым текстом: что в данной строчке кода содержится содержится потенциальная опасность народить кучу зомби процессов, а он за свое… мой скрипт, мой скрипт…
Конечно, если интенсивность их порождения не большая, то в принципе нет ничего страшного, но еще раз повторяю, что данный код не корректен, потому-что порождение нового процесса в бэкграундовском режиме заканчивается потерей родителя (родитель заканчивается раньше, чем его потомок) — это и приводит к зомбированию. Чтоб этого не произошло надо выполнить команду setsid() — которая в реализации данного режима не предусмотрена.
для того, чтоб запускать РНР в бэкграунде и не было побочных эффектов, специально придуман php-forker
— Зачем Вы доите лошадь? Для этого есть корова.
— Мы не доим лошадь, мы ездим на ней
— Говорю же, лошадь не для того, что бы ее доить, а вы все моя лошадь, моя лошадь…
А если конкретно, этот скрипт запускает несколько зомби процессов и завершает работу. Я не вижу тут демона, родитель и должен теряться. Данный код корректен для поставленной задачи.
Да, есть еще замечательная штука gaerman. Можно запустить кучу одинаковых процессов, а с помощью gaerman посылать задачи и ловить ответы.
Sign up to leave a comment.

Articles