
Я надеюсь что вы уже прочитали вводную статью про многопоточность в PHP, или вам это просто не требуется. Теперь я расскажу про счетчик, который будет доступен для потоков приложения, при этом доступ к нему будет эксклюзивным.
Разделяемая память - особая область памяти, которая доступа ВСЕМ процессам операционной системы. Доступ к определенному сегменту можно получить с помощью двух параметров: имени существующего файла и одного символа, который будет ключом System V IPC. Это нужно для того, чтобы несколько сегментов могли быть выделены основываясь на одном и том же файле. Звучит пока очень туманно, но сейчас я приведу пример, и надеюсь все станет понятно.
$t_key = ftok(__FILE__, 'g'); $shmid = shmop_open($t_key, 'c', 0755, 64); shmop_write($shmid, 1, 0); shmop_close($shmid);
Первой командой, специальной функцией ftok мы получили ключ, по которому будем получать доступ к сегменту разделяемой памяти. Первым параметром мы передали ему имя файла скрипта, а вторым букву 'g'. Почему 'g'? А просто так, можно взять любой символ.
Во второй строке мы собственно открываем доступ к сегменту памяти. Первый параметр - наш ключ, второй параметр 'c' - флаг указывающий на то, что мы создаем сегмент памяти, при том, если он уже существуем - он открывается на чтение/запись. Третий параметр - права на доступ, четвертый - размер в байтах.
В третьей строке мы записываем туда единицу, последний параметр - смещение.
И наконец закрываем сегмент.
Теперь по адресу, который определяется файлом скрипта и буквой 'g' в сегменте размером 64 байта лежит единица, и ее может достать любой процесс.
Теперь представим, что у нас программа работает в 50 потоков, она обрабатывает файл строчка за строчкой. Мы не хотим, чтобы программа два раза обрабатывала одну строку, поэтому кладем в разделяемую память номер строки, которая была обработана последней. И каждый процесс перед тем, как считать строчку берет из разделяемой памяти номер последней обработанной строки, прибавляет единицу, записывает обратно в разделяемую память и идет обрабатывать эту строчку. Следующий процесс возьмет уже строчку с номером на один больше. Все хорошо, все радуются, работает быстро. Однако случается так, что два процесса одновременно решили взять значение из памяти и взяли одинаковое значение и записали одинаковое. А представьте, что сразу 50 процессов одновременно считают значение. В общем дело ясное, что нам надо ограничить доступ к памяти одним процессом.
Как раз для таких случаев придуманы семафоры. Семафор (semaphore) - это такая сущность, которая позволяет ограниченному количеству процессов одновременно получать "зеленый свет" на выполнение чего-либо. Семафор, у которого такое количество равно 1 называется мьютексом (mutex). Именно мьютекс позволит нам не допускать в наш сегмент памяти больше одного процесса и остановит анархию.
$sem = sem_get(ftok(__FILE__, 'g'), 1); sem_acquire($sem); //тут может быть только один процесс sem_release($sem);
Мы получаем ключ для семафора точно таким же образом, как и для разделяемой памяти. Третий параметр - автоматическое "отпускание" семафора, если поток, который был впущен завершился. Те потоки, которые попытаются получить доступ к семафору, будут блокированы до тех пор, пока семафор не освободится. Это похоже на очередь в туалет. Один внутри, остальные снаружи. Один выходит, другой заходит и закрывается, другие продолжают ждать.
Таким образом, создав ячейку памяти и создав семафор мы получаем счетчик, который позволит нам обрабатывать файл построчно не боясь, что одна строка будет обработана два раза. Для использования этого принципа я сделал класс Counter, который работает в моем многопоточном приложении. Взять его можно . А тут я приведу пример использования:
//Ноль это наше начальное состояние счетчика $counter = Counter::_new('g', 0); //Порождаем потомков for ($i = 0; $i < PROCESS_AMOUNT; $i++) { $pid = pcntl_fork(); if ($pid == -1) { echo 'Error spawning new process'; } elseif ($pid) { //Родительский процесс у нас ничего не будет делать } else { //Первый параметр - имя счетчика, и заодно часть ключа //Второй параметр - увеличивать ли значение счетчика //В данном случае мы просто читаем значение $counter = Counter::get('g', false); //TOTAL_LINES - количество строк в файле while ($counter < TOTAL_LINES) { //Берем значение счетчика и увеличиваем его на 1 $counter = Counter::get('g', true); //Обарабатываем файл } } }
А еще я писал про:










Февраль 5th, 2010 at 16:43
Что будет если в участке кода с эксклюзивным доступом случится Exception?) Скорее всего функция sem_release($sem); никогда не вызовется и все потоки встанут перед дверью в туалет, в котором умер от передоза наркоман ^_^ .
Заворачивайте работу с блокировками в конструкцию try – catch – finally, чтоб гарантировать разблокирование критической секции.
Навряд-ли это произойдет автоматически, в конечном счете php использует ресурсы операционной системы для реализации многопоточности и средств синхронизации и корректно освободить семафор не сможет.
Спасибо. =)
Февраль 5th, 2010 at 16:47
Ценное замечание. Но если случится исключение – поток завершит работу, а я выставил параметр auto_release в 1. Так что наркоман автоматически будет выкинут.
А finally в php нету.