Совместное использование переменных между дочерними процессами в PHP? - PullRequest
3 голосов
/ 03 января 2012

Я уверен, что то, что я пытаюсь сделать, очень просто, но я никогда раньше не работал с многопоточностью, поэтому не уверен, с чего начать.

Я использую PCNTL для создания многопоточного приложения PHP. Я хочу, чтобы одновременно работали 3 функции, и я хочу, чтобы их возвращаемые значения были объединены в один массив. Поэтому, по логике вещей, мне нужна либо некоторая переменная, совместно используемая всеми дочерними элементами, к которой они добавляют свои результаты, либо три переменные, совместно используемые только одним дочерним элементом и родителем, - тогда родитель может объединить результаты позже.

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

Кроме того, если он имеет какой-либо эффект, функция, которая разветвляет процесс, является методом открытого класса. Мой код выглядит примерно так:

<?php
    class multithreaded_search {
        /* ... */
        /* Constructors and such */
        /* ... */
        public function search( $string = '' ) {
            $search_types = array( 'tag', 'substring', 'levenshtein' );
            $pids = array();
            foreach( $search_types as $type ) {
                $pid = pcntl_fork();
                $pids[$pid] = $type;
                if( $pid == 0 ) { // child process
                    /* confusion */
                    $results = call_user_func( 'multithreaded_search::'.$type.'_search', $string );
                    /* What do we do with $results ? */
                }
            }
            for( $i = 0; $i < count( $pids ); $i++ ) {
                $pid = pcntl_wait();
                /* $pids[$pid] tells me the type of search that just finished */
                /* If we need to merge results in the parent, we can do it here */
            }
            /* Now all children have exited, so the search is complete */
            return $results;
        }
        private function tag_search( $string ) {
            /* perform one type of search */
            return $results;
        }
        private function substring_search( $string ) {
            /* perform one type of search */
            return $results;
        }
        private function levenshtein_search( $string ) {
            /* perform one type of search */
            return $results;
        }
    }
?>

Так что мне нужно будет использовать shmop_open, прежде чем я вызову pcntl_fork, чтобы создать общую память и сохранить там результаты, или у детей общие переменные класса? Или они имеют только глобальные переменные? Я уверен, что ответ прост ... Я просто не знаю.

Ответы (для тех, кто находит это)

У меня есть еще несколько лет опыта, поэтому я постараюсь поделиться некоторыми знаниями.

Во-первых, есть два важных различия, которые нужно понимать, когда речь идет о реализации многопроцессорной обработки в ваших приложениях:

  • Резьбы против процессов против разветвленных процессов
  • Общая память против передача сообщений

Потоки, процессы, разветвленные процессы

  • Потоки : У потоков очень низкие накладные расходы, поскольку они выполняются в том же пространстве процессов, что и родительский, и совместно используют адрес памяти родителя. Это означает меньшее количество вызовов ОС для создания или уничтожения потока. Потоки являются «дешевой» альтернативой, если вы планируете их часто создавать и уничтожать. PHP не имеет встроенной поддержки потоков. Однако, начиная с PHP 7.2, существуют расширения PHP (написанные на C), которые обеспечивают многопоточную функциональность. Например: pthreads
  • Процессы : Процессы имеют гораздо большие издержки, потому что операционная система должна выделять для них память, а в случае интерпретируемых языков, таких как PHP, часто существует целая среда выполнения, которая должна быть загруженным и обработанным до выполнения вашего собственного кода. PHP имеет встроенную поддержку процессов порождения через exec (синхронно) или proc_open (асинхронно)
  • Разветвленные процессы : раздвоенный процесс разделяет разницу между этими двумя подходами. Отдельный процесс выполняется в области памяти текущих процессов. Существует также встроенная поддержка для этого через pctnl

Выбор правильного инструмента для работы часто сводится к задаче: «Как часто вы будете раскручивать дополнительные потоки / процессы»? Если это не так часто (возможно, вы запускаете пакетное задание каждый час, и задание может быть распараллелено), тогда процессы могут быть более простым решением. Если каждый запрос, поступающий на ваш сервер, требует какой-либо формы параллельных вычислений и вы получаете 100 запросов в секунду, то потоки, скорее всего, помогут вам.

Общая память, передача сообщений

  • Общая память : это когда нескольким потокам или процессам разрешено выполнять запись в один и тот же раздел ОЗУ. Преимущество состоит в том, что он очень быстрый и простой в понимании - он похож на общую доску в офисе. Любой может читать или писать на него. Однако он имеет несколько недостатков, когда речь идет об управлении параллелизмом. Представьте, что если два процесса пишут в одно и то же место в памяти в одно и то же время, тогда третий процесс пытается прочитать результат. Какой результат это увидит? PHP имеет встроенную поддержку разделяемой памяти через shmop , но для ее правильного использования требуются блокировки, семафоры, мониторы или другие сложные процессы системного проектирования
  • Передача сообщений : Это «горячая новая вещь» ™, которая существует с 70-х годов. Идея состоит в том, что вместо записи в общую память вы пишете в свое собственное пространство памяти, а затем говорите другим потокам / процессам: «Привет, у меня есть для вас сообщение». Язык программирования Go имеет известный девиз, связанный с этим: «Не общайтесь, разделяя память, делитесь памятью, общаясь». Существует множество способов передачи сообщений, в том числе: запись в файл, запись в сокет, запись в стандартный вывод, запись в общую память и т. Д.

Базовое решение для сокетов

Сначала я попытаюсь воссоздать свое решение с 2012 года. @MarcB указал мне на сокеты UNIX . На этой странице явно упоминается fsockopen , который открывает сокет как указатель файла. Он также включает в раздел «См. Также» ссылку на socket_connect , которая дает вам немного более низкий уровень контроля над сокетами.

В то время я, вероятно, проводил много времени, исследуя эти socket_* функции, пока не получил что-то работающее. Теперь я сделал быстрый поиск в Google для socket_create_pair и нашел эту полезную ссылку, чтобы вы начали

Я переписал приведенный выше код, записав результаты в сокеты UNIX и считав результаты в родительский поток:

<?php
/*
 * I retained the same public API as my original StackOverflow question,
 * but instead of performing actual searches I simply return static data
 */

class multithreaded_search {
    private $a, $b, $c;
    public function __construct($a, $b, $c) {
        $this->a = $a;
        $this->b = $b;
        $this->c = $c;
    }

    public function search( $string = '' ) {
        $search_types = array( 'tag', 'substring', 'levenshtein' );
        $pids = array();
        $threads = array();
        $sockets = array();
        foreach( $search_types as $type ) {
            /* Create a socket to write to later */
            $sockets[$type] = array();
            socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets[$type]);
            $pid = pcntl_fork();
            $pids[] = $pid;
            $threads[$pid] = $type;
            if( $pid == 0 ) { // child process
                /* no more confusion */
                $results = call_user_func( 'multithreaded_search::'.$type.'_search', $string );
                /* What do we do with $results ? Write them to a socket! */
                $data = serialize($results);
                socket_write($sockets[$type][0], str_pad($data, 1024), 1024);
                socket_close($sockets[$type][0]);
                exit();
            }
        }
        $results = [];
        for( $i = 0; $i < count( $pids ); $i++ ) {
            $pid = $pids[$i];
            $type = $threads[$pid];
            pcntl_waitpid($pid, $status);
            /* $threads[$pid] tells me the type of search that just finished */
            /* If we need to merge results in the parent, we can do it here */
            $one_result = unserialize(trim(socket_read($sockets[$type][1], 1024)));
            $results[] = $one_result;
            socket_close($sockets[$type][1]);
        }
        /* Now all children have exited, so the search is complete */
        return $results;
    }

    private function tag_search() {
        return $this->a;
    }

    private function substring_search() {
        return $this->b;
    }

    private function levenshtein_search() {
        return $this->c;
    }
}

$instance = new multithreaded_search(3, 5, 7);
var_dump($instance->search());

Примечания

В этом решении используются разветвленные процессы и передача сообщений через локальный (в памяти) сокет. В зависимости от вашего варианта использования и настройки, это может быть не лучшим решением. Например:

  • Если вы хотите разделить обработку между несколькими отдельными серверами и передать результаты обратно на центральный сервер, то create_socket_pair не будет работать. В этом случае вам нужно будет создать сокет, связать сокет с адресом и портом, затем вызвать socket_listen, чтобы дождаться результатов от дочерних серверов. Кроме того, pcntl_fork не будет работать в многосерверной среде, поскольку пространство процесса не может быть разделено между различными машинами
  • Если вы пишете приложение для командной строки и предпочитаете использовать потоки, то вы можете использовать pthreads или стороннюю библиотеку, которая абстрагирует pthreads
  • Если вам не нравится копаться в сорняках и просто хотите простую многопроцессорность, не беспокоясь о деталях реализации, загляните в библиотеку, подобную Amp / Parallel

Ответы [ 3 ]

5 голосов
/ 03 января 2012

разветвленных детей получат свою собственную выделенную копию своего пространства памяти, как только они напишут куда-нибудь в это - это «копирование при записи». Хотя shmop предоставляет доступ к общему месту в памяти, фактические переменные PHP и все, что не определено в сценарии, НЕ разделяются между дочерними элементами.

Выполнение $x = 7; на одном ребенке не приведет к тому, что $ x у других детей также станет 7. У каждого ребенка будет свой выделенный $ x, который полностью независим от копии другого.

1 голос
/ 03 января 2012

Пока папа и дети знают, что ключи / ключи сегмента разделяемой памяти можно делать shmop_open перед pcnlt_fork. Но помните, что pcnlt_fork возвращает 0 в дочернем процессе и -1 в случае невозможности создать дочерний процесс (проверьте код около комментария / confusion /). У отца будет в $ pid PID только что созданного дочернего процесса.

Проверьте это здесь:

http://php.net/manual/es/function.pcntl-fork.php

0 голосов
/ 05 декабря 2012

Используйте этот класс: http://pastebin.com/0wnxh4gY

http://framework.zend.com/manual/1.7/en/zendx.console.process.unix.overview.html

Он использует функции shm для совместного использования переменных во многих процессах с помощью метода setVariable ... очевидно, вы должны использовать его, используя PHP вкакой-то режим cgi скорее всего php-fpm

...