Основным минусом для меня стало слишком больше количество «наворотов» у этих решений — не всегда есть необходимость в обмене информации между потоками и родительским процессом или в экономии ресурсов. Всегда должна быть возможность быстро и с минимумом затрат решить задачу.
Заранее хочу оговориться, что в этом посте не открываются великие тайны — он скорее для новичков в языке, и опубликовать его я решил только потому, что в свое время сам столкнулся с проблемой и не найдя готового решения сделал эдакую эмуляцию многопоточности самостоятельно.
Итак, задача состоит в том, что бы обработать большое количество данных, пришедших в наш скрипт. Моей задачей было обработать JSON массив текстовой информации, переварив которую скрипт должен был собрать из неё не менее большой коммит для PostgreSQL.
Первым делом собираем данные в родительском файле:
index.php
// bigdata.json - файл с входными данными. Это может быть что угодно - файл, таблица в СуБД и т.д.
$big_json = file_get_contents('bigdata.json');
$items = json_decode($big_json, true);
// хоть в php и есть сборщик мусора, но лучше подчистить неиспользуемые, большие, хвосты
unset($big_json);
// ...
Размер массива колебался около 400мб (позже сократился до ~50мб), и вся информация была текстовой. Не сложно прикинуть скорость, с которой это всё переваривалось, а если учесть, что скрипт выполнялся по cron каждые 15 минут, а вычислительная мощность была такой себе — быстродействие страдало очень сильно.
После получения данных можно прикинуть их объем и при необходимости рассчитать необходимое количество потоков на каждое ядро ЦП, а можно просто решить, что потоков будет 4 и посчитать количество строк для каждого потока:
index.php
// ...
$threads = 4;
$strs_per_thread = ceil(count($items) / $threads);
// для запуска в ручном режиме - немного информации
echo "Items: ".count($items)."\n";
echo "Items per thread: ".$strs_per_thread."\n";
echo "Threads: ".$threads."\n";
// ...
Стоит сразу оговориться — такой расчет «в лоб» не даст точного результата по количеству элементов для каждого потока. Он нужен скорее для упрощения расчетов.
А теперь самая суть — создаем задачи для каждого потока и запускаем его. Делать мы это будем «в лоб» — создавая задачу для второго файла — thread.php. Он будет выступать в роли «потока», получая диапазон элементов для обработки и запускаясь независимо от основного скрипта:
index.php
// ...
for($i = 0; $i < $threads; $i++){
if($i == 0) {
passthru("(php -f thread.php 0 ".$strs_per_thread." & ) >> /dev/null 2>&1");
}
if($i == $threads-1) {
passthru("(php -f thread.php ".($strs_per_thread * $i)." ".count($items)." & ) >> /dev/null 2>&1");
}
if(($i !== 0)&&($i !== $threads-1)) {
$start = $strs_per_thread * $i + 1;
$end = $start -1 + $strs_per_thread;
passthru("(php -f thread.php ".$start." ".$end." & ) >> /dev/null 2>&1");
}
}
// ...
Функция passthru() используется для запуска консольных команд, но скрипт будет ждать окончания выполнения каждой из них. Для этого мы оборачиваем команду на запуск в набор операторов, которые запустят процесс и тут же вернут ничего, запустив процесс и родительский процесс не приостановится в ожидании выполнения каждого дочернего:
# вся магия, как это часто бывает, в самом Linux-е
(php -f thread.php start stop & ) >> /dev/null 2>&1
Что конкретно тут происходит, к сожалению, точно сказать не могу — набор параметров мне подсказал мой знакомый линуксоид. Если в комментах сможете расшифровать эту магию — буду признателен и дополню пост.
Файл thread.php:
$start = $argv[1];
$stop = $argv[2];
for ($i = $start; $i <= $stop; $i++) {
// какие-то действия с каждым элементом массива или строки из СуБД
}
Вот таким, довольно простым, способом можно реализовать эмуляцию многопоточности в PHP.
Если сократить весь пример до сухого вывода, то думаю он звучал бы так: родительский поток через командную строку запускает дочерние процессы, указывая им какую именно информацию обработать.
Говоря «эмуляцию» я имею в виду, что при таком методе реализации нет возможности для обмена информацией между потоками или между родительским и дочерними потоками. Он подходит в случае, если заранее известно, что такие возможности не нужны.
Комментариев нет:
Отправить комментарий