:-)
  • *nix, PHP 05.02.2010

    Я надеюсь что вы уже прочитали вводную статью про многопоточность в 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);
                //Обарабатываем файл
            }
        }
    }

    А еще я писал про:

    1. Многопоточность на PHP
    2. PHP: Получить строку по номеру
    3. Ошибка PDO Invalid Argument и Unix socket

    Tags: , , , ,

  • 2 комментариев

    WP_Modern_Notepad
    • Riot пишет:

      Что будет если в участке кода с эксклюзивным доступом случится Exception?) Скорее всего функция sem_release($sem); никогда не вызовется и все потоки встанут перед дверью в туалет, в котором умер от передоза наркоман ^_^ .

      Заворачивайте работу с блокировками в конструкцию try – catch – finally, чтоб гарантировать разблокирование критической секции.

      Навряд-ли это произойдет автоматически, в конечном счете php использует ресурсы операционной системы для реализации многопоточности и средств синхронизации и корректно освободить семафор не сможет.

      Спасибо. =)

    • CharnaD пишет:

      Ценное замечание. Но если случится исключение – поток завершит работу, а я выставил параметр auto_release в 1. Так что наркоман автоматически будет выкинут.

      А finally в php нету.

    Trackbacks

    Оставить комментарий

    Внимание: Комментарии проходят премодерацию. Не надо посылать их несколько раз.

    Это не спам.