Простой вариант реализации многопоточности на PHP

Многопоточность в PHP отсутствует «из коробки», поэтому вариантов её реализации было придумано великое множество, включая расширения pthreads, AzaThread (CThread), и даже несколько собственных наработок PHP программистов.

Основным минусом для меня стало слишком больше количество «наворотов» у этих решений — не всегда есть необходимость в обмене информации между потоками и родительским процессом или в экономии ресурсов. Всегда должна быть возможность быстро и с минимумом затрат решить задачу.

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

Итак, задача состоит в том, что бы обработать большое количество данных, пришедших в наш скрипт. Моей задачей было обработать 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.

Если сократить весь пример до сухого вывода, то думаю он звучал бы так: родительский поток через командную строку запускает дочерние процессы, указывая им какую именно информацию обработать.

Говоря «эмуляцию» я имею в виду, что при таком методе реализации нет возможности для обмена информацией между потоками или между родительским и дочерними потоками. Он подходит в случае, если заранее известно, что такие возможности не нужны.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *