Данная статья 2011 года является перепечаткой с ныне несуществующего блога http://raven.esiteq.com. Аминь, братья и сестры :)

В данной статье я расскажу, как без лишних усилий организовать многопоточность в PHP. В сети можно часто встретить бурление говн по поводу того, что в PHP нет нормальной многопоточности. Хотя она, конечно же, нужна. Почти ни один более-менее крупный проект не обходится без задач, которые нужно выполнять в несколько потоков. Примером тому может служить отправка писем с уведомлениями с крупного сайта знакомств. Если вам нужно отправить тысячу писем в день, то с этим прекрасно справится один поток. Но если это будет 100, а то и 500 тысяч уведомлений, то здесь без многопоточности никак. Скрипты, о которых пойдёт речь ниже, как раз и были разработаны для одного такого сайта с 5 миллионами зарегистрированных пользователей.

Итак, нам нужно три скрипта. Первый из них (start_threads.sh, шелл-скрипт), работает в кроне, и каждую минуту пытается запустить одновременно 10 потоков (кол-во потоков задаётся в переменной max). Минимальное количество потоков 1, максимальное нужно выбирать, исходя из конкретной задачи и мощности сервера. Для массовой рассылки, 10 потоков было оптимальным, увеличение их количества снижало скорость отправки из-за переполнения исходящей очереди почтового сервера (postfix).

#!/bin/bash
# здесь нам нужно указать полный путь к нашим скриптам, т.к этот скрипт запускается из крона
DIR=/path/to/your/scripts
cd $DIR

# кол-во одновременно запущенных тредов (10)
max=10
count=1

while [ $count -le $max ]
do
# запускаем thread.sh и передаём ему в качестве параметра номер треда
$DIR/thread.sh $count &
count=$(( $count + 1 ))
done

Второй скрипт, thread.sh, запускает PHP-тред и передаёт ему в качестве параметра номер треда. Он также следит за тем, чтобы каждый тред не был запущен более одного раза.

#!/bin/sh
# задаём полный путь к нашим скриптам
DIR=/path/to/your/scripts
myname=`basename $0`
if test -r $DIR/$myname_$1.pid; then
RPID=$(cat $DIR/$myname_$1.pid)
if $(kill -CHLD $RPID >/dev/null 2>&1)
then
exit 0
fi
fi
# store my pid
echo $$ > $DIR/$myname_$1.pid

PHP=/usr/bin/php
cd $DIR
# запускаем PHP тред и передаём ему в качестве параметра номер его треда, вывод записываем в лог
$PHP $DIR/thread.php $1 >> $DIR/thread_$1.log

Ну и собственно, сам PHP скрипт, который выполняет нужную нам задачу (отправку писем, и т.д.). Ему в качестве параметра передаётся номер треда. Данный пример не делает ничего, кроме как выводит сообщение “Тред такой-то запущен”, которое попадает в лог (для каждого треда лог отдельный).

...
// получаем номер треда
$thread = $argv[1];
// выводим тестовое сообщение, не забыв \n в конце, а то наш лог будет весь в одну строчку ;)
echo "Тред {$thread} запущен\n";
?>

Вот, собственно, и всё. Заливаем на сервер, не забывая сделать шелл-скрипты исполняемыми:

chmod +x ./*.sh

После чего, добавляем вызов start_threads.sh в крон.

И, на последок, яркий пример многопоточности :)