Есть ли в PHP потоки?


130

Я нашел этот пакет PECL под названием thread , но его пока нет. И на сайте PHP ничего не появляется.


Кто-нибудь знает, будет ли this ( pcntl_fork()) работать при вызове из Apache?
Josh K

Это невероятно старый вариант, но у меня есть ответ, который на самом деле обеспечивает многопоточность в php (ссылки см. Ниже).
Алек Гордж,

Они рекомендуют не вызывать fork из серверной среды. Я не виню их. Однако pcntl_fork действительно кажется лучшим решением для многопоточности PHP.
just_wes

Да, вам не нужно форкнуть процесс apache2 php.
andho

2
Использование pthreads работает как прелесть
Баба

Ответы:


40

Мне ничего не известно. Следующим лучшим вариантом было бы просто заставить один скрипт выполнять другой через CLI, но это немного элементарно. В зависимости от того, что вы пытаетесь сделать и насколько это сложно, это может быть или не быть вариантом.


1
Это то, о чем я думал. Я видел кучу старых сообщений, в которых говорилось «нет», и ничего на php.net, так что это была моя мысль. Спасибо за подтверждение.
Томас Оуэнс,

2
Да, этот пакет PECL - своего рода дразнилка - я тоже наткнулся на него, но из этого ничего не вышло.
Wilco

180

Из руководства PHP для расширения pthreads :

pthreads - это объектно-ориентированный API, который обеспечивает многопоточность на уровне пользователя в PHP. Он включает в себя все инструменты, необходимые для создания многопоточных приложений, ориентированных на Интернет или консоль. Приложения PHP могут создавать, читать, писать, выполнять и синхронизировать с потоками, рабочими процессами и стеками.

Как бы невероятно это ни звучало, это чистая правда. Сегодня PHP может быть многопоточным для желающих попробовать его.

Первый выпуск PHP4, 22 мая 2000 г., PHP поставлялся с потокобезопасной архитектурой - способом для выполнения нескольких экземпляров своего интерпретатора в отдельных потоках в многопоточных средах SAPI (Server API). В течение последних 13 лет конструкция этой архитектуры поддерживалась и совершенствовалась: с тех пор она используется в производственной среде на крупнейших веб-сайтах мира.

Многопоточность на уровне пользователя никогда не была проблемой для команды PHP, и остается таковой по сей день. Вы должны понимать, что в мире, где PHP занимается бизнесом, уже существует определенный метод масштабирования - добавление оборудования. За многие годы существования PHP оборудование стало все дешевле и дешевле, поэтому команда PHP все меньше и меньше беспокоилась об этом. Хотя он дешевел, он также становился намного мощнее; сегодня наши мобильные телефоны и планшеты имеют двух- и четырехъядерные архитектуры и достаточное количество оперативной памяти, наши настольные компьютеры и серверы обычно имеют 8 или 16 ядер, 16 и 32 гигабайта оперативной памяти, хотя мы не всегда можем иметь два в рамках бюджета и наличие двух рабочих столов редко бывает полезным для большинства из нас.

Кроме того, PHP был написан для непрограммистов, это родной язык для многих любителей. Причина того, что PHP так легко внедряется, заключается в том, что это простой язык для изучения и написания. Причина, по которой PHP сегодня так надежен, заключается в огромном объеме работы, которая требуется для его разработки, и в каждом отдельном решении, принимаемом группой PHP. Его надежность и абсолютное величие удерживают его в центре внимания после всех этих лет; где соперники упали из-за времени или давления.

Многопоточное программирование для большинства - непростая задача, даже с наиболее последовательным и надежным API, есть разные вещи, о которых нужно подумать, и множество заблуждений. Группа PHP не хочет, чтобы многопоточность была основной функцией пользователей, ей никогда не уделялось серьезного внимания - и это правильно. PHP не должен быть сложным для всех.

Учитывая все обстоятельства, есть еще преимущества, которые можно получить от разрешения PHP использовать готовые к производству и протестированные функции, позволяющие извлечь максимальную пользу из того, что у нас есть, когда добавление большего количества не всегда возможно, и для многих задач никогда не требуется.

pthreads предоставляет тем, кто желает изучить его, API, который позволяет пользователю создавать многопоточные приложения PHP. Это API, работа над которым находится в стадии разработки, он имеет бета-уровень стабильности и полноты.

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

pthreads использует потоки Posix (даже в Windows), то, что программист создает, являются реальными потоками выполнения, но для того, чтобы эти потоки были полезными, они должны знать о PHP - иметь возможность выполнять пользовательский код, совместно использовать переменные и предоставлять полезные средства связи. (синхронизация). Таким образом, каждый поток создается с помощью экземпляра интерпретатора, но по замыслу его интерпретатор изолирован от всех других экземпляров интерпретатора - точно так же, как среды многопоточного серверного API. pthreads пытается восполнить пробел разумным и безопасным способом. Многие из проблем программиста потоков на C просто не существуют для программиста pthreads, по замыслу pthreads копируется при чтении и копируется при записи (ОЗУ дешево), поэтому никакие два экземпляра никогда не манипулируют одними и теми же физическими данными. , но оба они могут влиять на данные в другом потоке.

Зачем копировать при чтении и копировать при записи:

public function run() {
    ...
    (1) $this->data = $data;
    ...
    (2) $this->other = someOperation($this->data);
    ...
}

(3) echo preg_match($pattern, $replace, $thread->data);

(1) Пока в хранилище данных объекта pthreads удерживаются блокировка чтения и записи, данные копируются из исходного местоположения в памяти в хранилище объектов. pthreads не регулирует счетчик ссылок переменной, Zend может освободить исходные данные, если на них больше нет ссылок.

(2) Аргумент someOperation ссылается на хранилище объектов, исходные сохраненные данные, которые сами являются копией результата (1), снова копируются для движка в контейнер zval, при этом блокировка чтения удерживается на хранилище объектов, блокировка снимается, и механизм может выполнять функцию. Когда zval создается, он имеет счетчик ссылок 0, что позволяет механизму освободить копию после завершения операции, поскольку других ссылок на него не существует.

(3) Последний аргумент preg_match ссылается на хранилище данных, получается блокировка чтения, набор данных в (1) копируется в zval, снова с refcount 0. Блокировка снимается, вызов preg_match работает с копия данных, которая сама по себе является копией исходных данных.

Что нужно знать:

  • Хэш-таблица хранилища объектов, в которой хранятся данные, потокобезопасная,
    основана на TsHashTable, поставляемой с PHP, компанией Zend.

  • Хранилище объектов имеет блокировку чтения и записи, для TsHashTable предоставляется дополнительная блокировка доступа, так что, если требуется (а это действительно так, var_dump / print_r, прямой доступ к свойствам, когда механизм PHP хочет ссылаться на них) pthreads могут управлять TsHashTable за пределами определенного API.

  • Блокировки удерживаются только тогда, когда происходят операции копирования, когда копии сделаны, блокировки снимаются в разумном порядке.

Это означает:

  • Когда происходит запись, удерживаются не только блокировка чтения и записи, но и дополнительная блокировка доступа. Сама таблица заблокирована, другой контекст не может заблокировать, прочитать, записать или повлиять на нее.

  • Когда происходит чтение, удерживается не только блокировка чтения, но и дополнительная блокировка доступа, таблица снова блокируется.

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

Это не разделяемая архитектура, и единственный способ существовать - сосуществовать. Те, кто немного подкован, увидят это, здесь происходит много копирования, и они задаются вопросом, хорошо ли это. В динамической среде выполнения происходит довольно много копирования, это динамика динамического языка. pthreads реализован на уровне объекта, потому что хороший контроль может быть получен над одним объектом, но методы - код, выполняемый программистом - имеют другой контекст, свободный от блокировок и копий - область локального метода. Область видимости объекта в случае объекта pthreads следует рассматривать как способ обмена данными между контекстами, то есть его цель. Имея это в виду, вы можете использовать методы, позволяющие избежать блокировки хранилища объектов, если в этом нет необходимости,

Большинство библиотек и расширений, доступных для PHP, представляют собой тонкие оболочки для сторонних разработчиков, функциональность ядра PHP в некоторой степени аналогична. pthreads - это не тонкая обертка вокруг Posix Threads; это потоковый API, основанный на Posix Threads. Нет смысла реализовывать потоки в PHP, которые пользователи не понимают или не могут использовать. Нет причин, по которым человек, не знающий, что такое мьютекс или что он делает, не должен иметь возможность использовать все, что у него есть, как с точки зрения навыков, так и с точки зрения ресурсов. Объект функционирует как объект, но там, где иначе столкнутся два контекста, pthreads обеспечивает стабильность и безопасность.

Любой, кто работал с java, увидит сходство между объектом pthreads и потоком в java, те же самые люди, без сомнения, увидят ошибку под названием ConcurrentModificationException - поскольку это звучит как ошибка, вызванная средой выполнения java, если два потока записывают одни и те же физические данные одновременно. Я понимаю, почему он существует, но меня сбивает с толку то, что с такими дешевыми ресурсами, в сочетании с тем фактом, что среда выполнения может обнаруживать параллелизм в точное и единственное время, когда безопасность может быть достигнута для пользователя, который он выбирает для генерировать возможно фатальную ошибку во время выполнения, а не управлять выполнением и доступом к данным.

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

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

Наконец, из руководства PHP:

pthreads был и остается экспериментом с довольно хорошими результатами. Любые его ограничения или функции могут измениться в любое время; это природа экспериментирования. Его ограничения - часто налагаемые реализацией - существуют не зря; цель pthreads - предоставить удобное решение для многозадачности в PHP на любом уровне. В среде, в которой выполняется pthreads, необходимы некоторые ограничения и ограничения, чтобы обеспечить стабильную среду.


14
Полная чепуха.
Джо Уоткинс

7
@GeoC. Я даже не уверен, о чем вы здесь говорите, это просто тарабарщина, и вы не приводите никаких причин , логических или иных, почему вы не согласны с какими-либо аргументами (из которых я действительно не вижу их в сообщении) ,
Jimbo

13
@Tudor Я не думаю, что ты действительно понимаешь, о чем говоришь, поэтому я счастлив игнорировать тебя.
Джо Уоткинс

4
@Tudor - многие ученые не «видели смысла» в чем-то новом в своей отрасли или в чем-то полезном. История показала, что чаще всего такие люди, как вы, просто ошибаются, это факт. Точно так же, как факт, что все, что вы здесь написали, - это, если не сказать лучшего слова, фекалии. Я настоятельно рекомендую, имея в виду все позитивное, не участвовать в темах, о которых вы ничего не знаете (это одна из них).
NB

12
Забавная вещь. Джо Уоткинс - автор pthreads, но Тюдор все еще пытается доказать, что он неправ.
Христо Валканов

48

Вот пример того, что предложил Вилко:

$cmd = 'nohup nice -n 10 /usr/bin/php -c /path/to/php.ini -f /path/to/php/file.php action=generate var1_id=23 var2_id=35 gen_id=535 > /path/to/log/file.log & echo $!';
$pid = shell_exec($cmd);

В основном это выполняет сценарий PHP в командной строке, но сразу возвращает PID, а затем запускается в фоновом режиме. (Echo $! Гарантирует, что ничего не будет возвращено, кроме PID.) Это позволяет вашему PHP-скрипту продолжить или завершить работу, если вы хотите. Когда я использовал это, я перенаправил пользователя на другую страницу, где каждые 5-60 секунд выполняется вызов AJAX, чтобы проверить, работает ли отчет по-прежнему. (У меня есть таблица для хранения gen_id и пользователя, с которым он связан.) Скрипт проверки выполняет следующее:

exec('ps ' . $pid , $processState);
if (count($processState) < 2) {
     // less than 2 rows in the ps, therefore report is complete
}

Здесь есть небольшая статья об этой технике: http://nsaunders.wordpress.com/2007/01/12/running-a-background-process-in-php/


У меня небольшая проблема, как проверить статус фонового процесса? Ты можешь просветить меня?
slier

25

Вкратце: да, в php есть многопоточность, но вместо этого вы должны использовать многопроцессорность.

Справочная информация: потоки против процессов

Всегда существует некоторая путаница в различиях потоков и процессов, поэтому я кратко опишу оба:

  • Поток представляет собой последовательность команд , которые будут обрабатывать процессор. Единственные данные, из которых он состоит, - это счетчик программ. Каждое ядро ​​ЦП будет обрабатывать только один поток за раз, но может переключаться между выполнением разных с помощью расписания.
  • Процесс представляет собой набор общих ресурсов. Это означает, что он состоит из части памяти, переменных, экземпляров объектов, дескрипторов файлов, мьютексов, соединений с базой данных и так далее. Каждый процесс также содержит один или несколько потоков. Все потоки одного процесса совместно используют свои ресурсы, поэтому вы можете использовать переменную в одном потоке, которую вы создали в другом. Если эти потоки являются частями двух разных процессов, они не могут напрямую обращаться к ресурсам друг друга. В этом случае вам понадобится межпроцессное взаимодействие, например, через каналы, файлы, сокеты ...

многопроцессорная обработка

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

В php у вас есть два способа создать новый процесс:

пусть ОС сделает это за вас : вы можете указать своей операционной системе создать новый процесс и запустить в нем новый (или тот же) скрипт php.

  • для linux вы можете использовать следующее или рассмотреть ответ Даррила Хайна :

    $cmd = 'nice php script.php 2>&1 & echo $!';
    pclose(popen($cmd, 'r'));
  • для окон вы можете использовать это:

    $cmd = 'start "processname" /MIN /belownormal cmd /c "script.php 2>&1"';
    pclose(popen($cmd, 'r'));

сделайте это самостоятельно с помощью вилки : php также предоставляет возможность использовать вилку с помощью функции pcntl_fork () . Хорошее руководство о том, как это сделать, можно найти здесь, но я настоятельно рекомендую не использовать его, поскольку вилка - это преступление против человечества и особенно против oop.

Многопоточность

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

Стандартный php не обеспечивает многопоточности, но есть (экспериментальное) расширение, которое действительно поддерживает - pthreads . Его документация по api даже попала в php.net . С его помощью вы можете делать некоторые вещи, которые вы можете делать в реальных языках программирования :-), например:

class MyThread extends Thread {
    public function run(){
        //do something time consuming
    }
}

$t = new MyThread();
if($t->start()){
    while($t->isRunning()){
        echo ".";
        usleep(100);
    }
    $t->join();
}

Для Linux есть руководство по установке прямо здесь, на stackoverflow's.

Для окон теперь есть один:

  • Сначала вам нужна поточно-ориентированная версия php.
  • Вам нужны предварительно скомпилированные версии как pthreads, так и его расширения php. Их можно скачать здесь . Убедитесь, что вы загружаете версию, совместимую с вашей версией php.
  • Скопируйте php_pthreads.dll (из только что загруженного zip-архива) в папку расширения php ([phpDirectory] / ext).
  • Скопируйте pthreadVC2.dll в [phpDirectory] (корневую папку, а не папку расширения).
  • Отредактируйте [phpDirectory] /php.ini и вставьте следующую строку

    extension=php_pthreads.dll
  • Проверьте это с помощью приведенного выше сценария, немного поспав или что-то еще прямо там, где есть комментарий.

А теперь большое НО : хотя это действительно работает, php изначально не был предназначен для многопоточности. Существует поточно-ориентированная версия php, и с v5.4 она кажется почти безошибочной, но использование php в многопоточной среде все еще не рекомендуется в руководстве по php (но, возможно, они просто не обновили свое руководство по это, пока). Гораздо более серьезная проблема может заключаться в том, что многие распространенные расширения не являются потокобезопасными . Таким образом, вы можете получать потоки с этим расширением php, но функции, от которых вы зависите, по-прежнему не являются потокобезопасными, поэтому вы, вероятно, столкнетесь с условиями гонки, взаимоблокировками и т. Д. В коде, который вы не писали сами ...


5
Это ужасно неправильно, статья, на которую вы ссылаетесь, относится к 2008 году. Если бы PHP не был потокобезопасным в своей основе, он не имел бы многопоточных модулей SAPI.
Джо Уоткинс,

1
@Joe: Хорошо, я изменил его: ядро ​​ориентировано на многопотоковое исполнение, но многие расширения - нет.
Francois Bourgeois

1
Много? Я думаю, вы найдете их очень мало, вы нашли документацию, но не смогли ее правильно прочитать: Примечание. Те, которые отмечены *, не являются поточно-ориентированными библиотеками, и их не следует использовать с PHP в качестве серверного модуля в многопоточном -поточные веб-серверы Windows (IIS, Netscape). Пока это не имеет значения в средах Unix.
Джо Уоткинс,

6
PHP очень потокобезопасен, и в течение многих лет некоторые из внешних библиотек и несколько связанных с ними не таковыми, но он хорошо документирован и в любом случае довольно очевиден. pthreads создает потоки, которые так же безопасны, как потоки, созданные zend в многопоточном сапи, я знаю это, потому что я один написал pthreads. Он использует все доступные API, предоставляемые PHP, так же, как и серверные API. Я не говорю, что он полностью стабилен, но нарисованная вами картина просто неверна и очень плохо информирована.
Джо Уоткинс

@Joe: Когда в руководстве говорится, что это не имеет значения для сред Unix, они имеют в виду тот факт, что в системе Unix apache использует несколько процессов, а в Windows - потоки. По сути, они говорят: «Если вы все равно не используете потоки, вам не нужно беспокоиться о расширениях, не поддерживающих потоки». Когда мы используем потоки с pthreads, это, конечно же, имеет значение и в средах Unix.
Francois Bourgeois

17

Вы можете использовать pcntl_fork () для достижения чего-то похожего на потоки. Технически это отдельные процессы, поэтому связь между ними не так проста с потоками, и я считаю, что это не будет работать, если PHP будет вызван apache.


4
Я успешно использую pcntl_fork для распараллеливания довольно массивной задачи импорта данных. Отлично работает, и он работал примерно через час. Есть некоторая кривая обучения, но как только вы понимаете, что происходит, все довольно просто.
Фрэнк Фармер

Фрэнк, это с CLI php или apache PHP?
Артем Русаковский

@ Артем: Я тоже хотел бы знать.
Josh K

5
@Frank Farmer дразнит нас ... прямо как пакет PECL.
Роджер,

1
Я использовал pcntl_fork с CLI. Никогда не пробовал в apache; это звучит рискованно. Даже в интерфейсе командной строки возникли неожиданные проблемы. Кажется, у меня возникла проблема, когда, если один дочерний элемент закрыл дескриптор базы данных (потому что он завершил свою работу), он также закрыл соединение для братьев и сестер. Поскольку дети являются копиями родителей, приготовьтесь к странностям. С тех пор я переработал свой код, чтобы просто порождать новые, полностью отдельные процессы через exec () - так он стал чище.
Фрэнк Фармер,


7

pcntl_fork()это то, что вы ищете, но его процесс разветвляется, а не распределяется. так что у вас возникнет проблема обмена данными. для их решения вы можете использовать семафорные функции phps ( http://www.php.net/manual/de/ref.sem.php ), очереди сообщений могут быть немного проще для начала, чем сегменты разделяемой памяти.

В любом случае, стратегия, которую я использую в разрабатываемой мной веб-структуре, которая загружает ресурсоемкие блоки веб-страницы (возможно, с внешними запросами) параллельно: я делаю очередь заданий, чтобы узнать, какие данные я жду, а затем я разветвляю от рабочих мест для каждого процесса. после этого они сохраняют свои данные в кэше apc под уникальным ключом, к которому родительский процесс может получить доступ. как только все данные будут там, это продолжается. Я использую простое usleep()ожидание, потому что межпроцессное взаимодействие невозможно в apache (дети теряют связь со своими родителями и становятся зомби ...). Итак, это подводит меня к последнему: важно убить каждого ребенка самостоятельно! Есть также классы, которые обрабатывают форк, но хранят данные, я не проверял их, но у zend framework есть один, и они обычно делают медленный, но надежный код. Вы можете найти это здесь: http://zendframework.com/manual/1.9/en/zendx.console.process.unix.overview.html я думаю, что они используют сегменты shm! Ну и последнее, но не менее важное: на этом веб-сайте zend есть ошибка, небольшая ошибка в примере.

while ($process1->isRunning() && $process2->isRunning()) {
    sleep(1);
}
should of course be:
while ($process1->isRunning() || $process2->isRunning()) {
    sleep(1);
}



5

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

РЕДАКТИРОВАТЬ: теперь это доступно как библиотека композитора и как часть моей инфраструктуры MVC, Hazaar MVC.

См .: https://git.hazaarlabs.com/hazaar/hazaar-thread


Что, если, следуя вашему примеру, программа в файле file.php, скажем, например, определяет существование списка из 10 000 uris веб-сайтов, а затем должна сохранить результат в файле CSV ... Будет ли запись этого файла проблема?
Роджер,

Подпроцесс будет запускаться от имени того же пользователя, что и веб-сервер / родительский скрипт. Поэтому при записи файлов у вас будут те же соображения относительно разрешений, что и обычно. Если у вас есть проблемы с записью файлов, попробуйте записать в / tmp, а когда это сработает, переходите оттуда.
Джейми Карл

1
Ссылка теперь мертва из-за редизайна, вы можете получить ее на машине возврата здесь: web.archive.org/web/20130922043615/http://dev.funkynerd.com/…
Тони

Добавлен в мою структуру MVC. См .: git.hazaarlabs.com/hazaar/hazaar-thread
Джейми Карл


1

Вы когда-нибудь слышали об этом appserverот Techdivision?

Он написан на php и работает как сервер приложений, управляющий многопоточностью для приложений php с высоким трафиком. Все еще находится в стадии бета-тестирования, но очень продвигается.


-3

Есть довольно малоизвестная и скоро будет устаревшая функция под названием тики . Единственное, для чего я когда-либо его использовал, - это позволить скрипту захватить SIGKILL (Ctrl + C) и изящно завершить работу.


3
Тики не выполняются параллельно. По сути, после каждого оператора ваша функция тика запускается. Пока ваша функция тика работает, основной код не работает.
Фрэнк Фармер

1
тики нужны только для обработчика signal ().
Ник
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.