Контакты

Панические расстройства. За какие поступки, совершенные в нетрезвом виде, вам очень стыдно? Обработка разовых задач


02.10.2011, 21:27

За то что избила мужика и повесила его футболку на дерево.И парня придурка одного гнала пару киллометров а когда он упал стала на него писать.Но они это заслужили.

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

После последней пьянки мне сдытно:
1. орала с балкона мужику “отсоси“
2. сожрала сигарету
3. швырялась яблоками
4. звонила парню и рассказывала,шо я секса ему не дам в грубой форме
5. топила бычки в грейпфрутовом соке
6. звонила маме и рассказывала,что я трезвая
7. ораза парню подруги,чтоб он принес нам вина
8. написала мимо унитаза,так как их было 2-выбрала я не тот,а потом упала
9. выпала с ванной

Я с ножом за мужем по квартире 1 раз бегала, хотя выпила шампанского пару бокалов. Наутро страшно стало - а вдруг бы заехала ножом, я же просто решила попугать. И не знаю с чего на меня такой заскок нашел!

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

Ко мне как-то парень клеился,а я такая нажранная была,что начала ему популярно объяснять,мол ничего не обломится,потому что я “кровавая Мэри“ (кд были) и мне эта кликуха так в душу запала,что я чуть ли не каждому встречному кричала:“я - кровавая Мэри“ благо,что это было не в моем родном городе

-*звонила бывшему, высказывала о нём всё, что думаю
*звонила любимому, говорила, как я его хочу
*съела шаурму с фольгой
*обматерила парня, с которым в клубе познакомилась, хотя он мне помогал, водичку приносил
*танцевала стриптиз в клубе в конкурсе, получила второе место
*кидалась льдом в незнакомого парня
*друг моего мужчины на жёлтом хамере ездеет. таких в Корее я больше ни у кого не видела. когда мы все вместе поехали тусить. я высунулась из машины своего мужчины, и, показывая на впереди едущий хамер, орала, что эта машина моего друга
*вчера я была в клубе. когда ехала утром домой на такси, сказала водителю: [перевод с корейскиго. дословный!] “Еда, которую я съела, сказала мне только что, что хочет выйти наружу!“
*ещё у меня друг монгол есть. я к нему на спину забралась, заставила его возить меня на спине и на всю улицу орала, что он моя монгольская лошадь
а вообще ещё много всего было, так и не вспомнишь...

Парню написала в 4 часа утра “спокойной ночи, дорогой“

Напились с подругами коньяка,все разъехались по домам,а я дура вызвала такси(было 3 часа ночи) и поехала к бывшему парню с которым рассталась 4 месяца назад(остались друзьями).Не помню как попала в подъезд,добралась до его квартиры,стала ломиться к нему в дверь и орать“ЖЕНИСЬ НА МНЕ МАРАТ“ ,он бедный охренел.Затащил меня домой и под душ холодный,напоил крепким чаем.Через часа два я отошла,а потом когда сообразила что сделала МНЕ БЫЛО НЕРЕАЛЬНО СТЫДНО.После этого мы помирились и до сих пор вместе. После этого больше бокала шампанского не пью.


...

02.10.2011, 22:09

Убитая шла с парнем через сад и тут мне показалась, что он как-то меня обидел тем, что шел на несколько шагов впереди меня, а не рядом. Я обиделась и залезла на дерево, а он и не заметил...он часа полтора бегал по саду и искал меня.....а я преспокойно вырубилась на дереве и проспала до утра. ...
...
Зато утром сразу не смогла вспомнить как я очутилась на ветвях. Сты-ыыыыы-дно то как было после
;D;D;D

Интерпретация

02.10.2011, 22:42

мне стыдно за нек поступки, совершенные в трезвом виде:) Но это ближе к философии. В нетрезвом...оч многое было весело, стыдно..нет, сорри)))))

Елена Lotus

02.10.2011, 23:17

Вчера после того как я поняла что мой телефон(ООО!!!)теперь не мой я напилась.сильно.причем(вот кто меня знает-не говорите что я пью как мышь)потом совершила такое...что только близким друзьям по большому секрету и то я подумаю кому можно рассказать...Ресторан "Паберти"был опять шокирован.Теперь смотрю фотогалерею в надежде увидеть проишедшее:(,но не стыдно...нет...просто я думала что я приличная.

02.10.2011, 23:31

;D;D;D
Я всегда утверждал, что тётькам пить нельзя!!! ВСЕМ!!!;)

..;D;D;D истину говоришь

02.10.2011, 23:36

Пьяная женщина - как китайский пуховик: мягкая и лезет.
Галыгин

Елена Lotus

02.10.2011, 23:41

а мужчинам типа можно да?Пить можно всем. и тетькам и дядькам...только надо аккуратненько так...сегодня голова не болит..душа болит...напится?прям замкнутый круг;)

03.10.2011, 00:15

Пейте все и всё, пока молоды и есть здоровье:)

03.10.2011, 01:48

выборочно сперто с женского форума:)

Чуть не написала в корзину для белья, при маме..... попутала с унитазом.... упс....

Убитая шла с парнем через сад и тут мне показалась, что он как-то меня обидел тем, что шел на несколько шагов впереди меня, а не рядом. Я обиделась и залезла на дерево, а он и не заметил...он часа полтора бегал по саду и искал меня.....а я преспокойно вырубилась на дереве и проспала до утра. ...
...
Зато утром сразу не смогла вспомнить как я очутилась на ветвях. Сты-ыыыыы-дно то как было после

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

03.10.2011, 05:35

Напились с подругами коньяка,все разъехались по домам,а я дура вызвала такси(было 3 часа ночи) и поехала к бывшему парню с которым рассталась 4 месяца назад(остались друзьями).Не помню как попала в подъезд,добралась до его квартиры,стала ломиться к нему в дверь и орать“ЖЕНИСЬ НА МНЕ МАРАТ“ ,он бедный охренел.Затащил меня домой и под душ холодный,напоил крепким чаем.Через часа два я отошла,а потом когда сообразила что сделала МНЕ БЫЛО НЕРЕАЛЬНО СТЫДНО.После этого мы помирились и до сих пор вместе. После этого больше бокала шампанского не пью.

Вот вполне реальная история любви.
И о пользе пьянства тоже.

(Пейте, девочки, и приидет вам щастье.)

В сети гуляет довольно много решений для эмуляции многопоточности в php. Чаще всего они основываются на форках, но есть и вариации на тему с использованием curl , proc_open и т.п.

Все встреченные варианты по тем или иным причинам меня не устроили, и пришлось написать своё решение. Набор требований у меня был следующий:

  • Использование форков;
  • Синхронный режим с сохранением интерфейса при отсутствии необходимых расширений;
  • Многократное использование дочерних процессов;
  • Полноценный обмен данными между процессами. Т.е. запуск с аргументами и получение результата по завершении;
  • Возможность обмена событиями между дочерним процессом-«потоком» и основным процессом во время работы;
  • Работа с пулом потоков с сохранением многократного использования, передачи аргументов и получения результатов;
  • Обработка ошибок выполнения;
  • Таймауты на выполнение работы, ожидание работы потоком, инициализацию;
  • Максимум производительности.

В результате получилась библиотека AzaThread (старое название — CThread).

Описание

AzaThread предоставляет простой интерфейс для создания классов-потоков. Которые на самом деле используют отдельные процессы для асинхронной работы, но вас это не должно волновать. Вы можете посылать события из потока, возвращать результаты, использовать один поток множество раз, передавая ему аргументы запуска, или создать пул из 16 потоков разгребающих ваши задачи как горячие пирожки, не обращая никакого внимания на то, что работа происходит в разных процессах.

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

Для полноценной работы требуются следующие расширения: libevent , posix и pcntl .

Библиотека использует LibEvent и парные сокеты для общения между процессами. Поддерживает 5 вариантов передачи данных (аргументов, результатов и данных событий)!

Варианты привожу сразу с данными производительности. Тестировалось с пулом из восьми потоков на Intel Core i7 2600K 3.40 Ghz (Ubuntu 11.04 на виртуалке VMware). Приведены средние результаты за 10 повторов теста в jps (jobs per second — кол-во задач просто получающих аргументы и отдающих данные в секунду).

Автоматически выбирается расширение для работы с сокетами. Если доступно, то используется расширение sockets , что дает улучшение производительности. В ином случае задействуется stream .

В дочернем процессе слушаются все доступные сигналы. По умолчанию на все из них (кроме SIGWINCH и SIGINFO) следует завершение работы. Но это легко можно переопределить создав в классе потока метод с именем сигнала. Например sigWinch .

В родительском процессе по умолчанию тоже перхватываются все сигналы. Это можно изменить, выставив у класса параметр listenMasterSignals в false . В этом случае обрабатываться будет только SIGCHLD . Свои собственные обработчики можно легко добавить, создав статический метод с названием m<имя сигнала> . Например, mSigTerm .

Если дочерний процесс умрёт по каким-либо причинам, класс автоматически форкнется при запуске новой задачи. Это происходит незаметно и можно вообще об этом не задумываться. Просто инстанс не нужно пересоздавать в случае какой-либо ошибки.

Дочерний процесс время от времени проверяет существование родительского. Если он вдруг помрёт, то дочерний автоматический завершится.

Все ресурсы, используемые потоком или пулом потоков, автоматически очищаются при вызове деструктора. Но их можно очистить принудительно, если вызывать метод cleanup . В этом случае поток/пул больше нельзя использовать.

При стандартных настройках поток инициализируется заранее, сразу при создании класса. Если установить параметр prefork в false , то форк будет происходить только в момент запуска задачи.

Вообще, настраиваемых параметров довольно много. Смена имени дочернего процесса после форка (параметр pName конструктора), таймаут на время выполнения задачи (timeoutWork), таймаут на максимальное время ожидания задач дочерним процессом (timeoutMaxWait), таймаут на время пре-инициализации (timeoutInit), размеры буферов для чтения сокетов (pipeReadSize , pipeMasterReadSize). Можно отключить режим мультизадачности для потоков (multitask). В этом случае каждый раз по завершении задачи дочерний процесс будет умирать и форкаться заново для следующего запуска. Это заметно уменьшит производительность.

Код покрыт тестами и подробно документирован, примеры использования можно посмотреть и запустить в файле example.php . Более сложные примеры с обработкой ошибок можно увидеть в коде юнит-теста.

Есть режим отладки, в котором выводится очень подробная информация о том, что именно и где творится.

Примеры использования

Основная фича — максимальная простота. Если вы хотите просто запустить что-либо в отдельном «потоке» достаточно следующего кода:

Class ExampleThread extends Thread { protected function process() { // Some work here } } $thread = new ExampleThread(); $thread->wait()->run();

Если есть всё необходимое для полноценной работы, то задача будет выполнена асинхронно. Если нет, то всё будет по-прежнему работать, но в синхронном режиме.

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

Class ExampleThread extends Thread { protected function process() { return $this->getParam(0); } } $thread = new ExampleThread(); $thread->wait()->run(123); $result = $thread->wait()->getResult();

Аналогично, лёгким мановением руки, добавляем обработку событий из потока:

Class ExampleThread extends Thread { const EV_PROCESS = "process"; protected function process() { $events = $this->getParam(0); for ($i = 0; $i trigger(self::EV_PROCESS, $event_data); } } } // Дополнительный аргумент. $additionalArgument = 123; $thread->bind(ExampleThread::EV_PROCESS, function($event_name, $event_data, $additional_arg) { // обработка события }, $additionalArgument); $events = 10; // число событий, которое сгенерирует поток // Чтобы не вызывать вручную ожидание потока перед первым вызовом, // можно переопределить свойство preforkWait в TRUE в классе-наследнике $thread->wait(); $thread = new ExampleThread(); $thread->run($events)->wait();

И, наконец, использование пула из восьми потоков с обработкой ошибок выполнения:

$threads = 8 // Число потоков $pool = new ThreadPool("ExampleThread", $threads); $num = 25; // Количество задач $left = $num; // Количество оставшихся задач do { // Если в пуле есть свободные потоки // И у нас остались задачи для выполнения while ($pool->hasWaiting() && $left > 0) { // При запуске получаем id потока $threadId = $pool->run(); $left--; } if ($results = $pool->wait($failed)) { foreach ($results as $threadId => $result) { // Успешно выполненная задача // Результат можно идентифицировать // по id потока ($threadId) $num--; } } if ($failed) { // Обработка ошибок выполнения. // Работа считается завершенной неуспешно // если дочерний процесс умер во время выполнения или // истек таймаут на выполнение задачи foreach ($failed as $threadId) { $left++; } } } while ($num > 0); // Завершаем все дочерние процессы. Очищаем ресурсы используемые пулом. $pool->cleanup();

Результаты тестирования производительности

Тесты запускал на двух машинах с Ubuntu 11.04.
Первая — Intel Core i3 540 3.07 Ghz.
Вторая — Intel Core i7 2600K 3.40 Ghz (убунту стоит на VMware виртуалке).

Результаты привожу просто чтобы можно было оценить рост производительности. Опять же, это средние результаты за серию из 10 повторов теста в jps (jobs per second — кол-во задач в секунду).

В качестве задачи потоки выполняют следующую фигню:

For ($i = 0; $i

Первый результат указан для синхронного режима работы (без форков). 18 и 20 потоков на первой конфигурации я не пробовал, так как уже для 12 началось падение производительности.

Число потоков Первая конфигурация Вторая
0 553 763
1 330 669
2 580 1254
4 1015 2188
8 1040 2618
10 1027 2719
12 970 2739
16 958 2904
18 - 2830
20 - 2730

То есть производительность поднимается в 2-4 раза и более в зависимости от процессора!

Код, выполняющий серию тестов с нужными параметрами, лежит в файле examples/speed_test.php . Так что вы легко можете потестировать производительность и выбрать оптимальное число потоков у себя.

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

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

В этой статье мы взглянем на то, как многопоточность может быть достигнута в PHP с помощью расширения pthreads . Для этого потребуется установленная ZTS (Zend Thread Safety) версия PHP 7.x, вместе с установленным расширением pthreads v3. (На момент написания статьи, в PHP 7.1 пользователям нужно будет установить из ветки master в репозитории pthreads - см. стороннее расширение.)

Небольшое уточнение: pthreads v2 предназначена для PHP 5.x и более не поддерживается, pthreads v3 - для PHP 7.х и активно развивается.

После такого отступления, давайте сразу перейдём к делу!

Обработка разовых задач

Иногда вы хотите обрабатывать разовые задачи многопоточным способом (например, выполнение некой задачи, завязанной на ввод-вывод). В таких случаях можно использовать класс Thread , чтобы создать новый поток и запустить некую обработку в отдельном потоке.

Например:

$task = new class extends Thread { private $response; public function run() { $content = file_get_contents("http://google.com"); preg_match("~(.+)~", $content, $matches); $this->response = $matches; } }; $task->start() && $task->join(); var_dump($task->response); // string(6) "Google"

Здесь метод run — это наша обработка, которая будет выполняться внутри нового потока. При вызове Thread::start , порождается новый поток и вызывается метод run . Затем мы присоединяем порождённый поток обратно в основной поток, вызвав Thread::join , который будет заблокирован до тех пор, пока порождённый поток не завершит своё выполнение. Это гарантирует, что задача завершит выполнение, прежде чем мы попытаемся вывести результат (который хранится в $task->response).

Возможно, не желательно загрязнять класс дополнительной ответственностью, связанной с логикой потока (в том числе обязанность определения метода run). Мы можем выделить такие классы, унаследовав их от класса Threaded . Тогда они могут быть запущены внутри другого потока:

Class Task extends Threaded { public $response; public function someWork() { $content = file_get_contents("http://google.com"); preg_match("~ (.+) ~", $content, $matches); $this->response = $matches; } } $task = new Task; $thread = new class($task) extends Thread { private $task; public function __construct(Threaded $task) { $this->task = $task; } public function run() { $this->task->someWork(); } }; $thread->start() && $thread->join(); var_dump($task->response);

Любой класс, который должен быть запущен в отдельном потоке, должен наследоваться от класса Threaded . Это потому что он предоставляет необходимые возможности для выполнения обработки в разных потоках, а также неявную безопасность и полезные интерфейсы (такие, как синхронизация ресурсов).

Давайте взглянем на иерархию классов, предлагаемую расширением pthreads:

Threaded (implements Traversable, Collectable) Thread Worker Volatile Pool

Мы уже рассмотрели и узнали основы классов Thread и Threaded , теперь давайте взглянем на остальные три (Worker , Volatile , и Pool).

Переиспользование потоков

Запуск нового потока для каждой задачи, которую нужно распараллелить, достаточно затратно. Это потому что архитектура "ничего-общего" должна быть реализована в pthreads, чтобы добиться многопоточности внутри PHP. Что означает, что весь контекст выполнения текущего экземпляра интерпретатора PHP (в том числе и каждый класс, интерфейс, трейт и функция) должна быть скопирована для каждого созданного потока. Поскольку это влечет за собой заметное влияние на производительность, поток всегда должен быть повторно использован, когда это возможно. Потоки могут быть переиспользованы двумя способами: с помощью Worker -ов или с помощью Pool -ов.

Класс Worker используется для выполнения ряда задач синхронно внутри другого потока. Это делается путем создания нового экземпляра Worker -а (который создает новый поток), а затем внесением задач в стек этого отдельного потока (с помощью Worker::stack).

Вот небольшой пример:

Class Task extends Threaded { private $value; public function __construct(int $i) { $this->value = $i; } public function run() { usleep(250000); echo "Task: {$this->value}\n"; } } $worker = new Worker(); $worker->start(); for ($i = 0; $i stack(new Task($i)); } while ($worker->collect()); $worker->shutdown();

В вышеприведённом примере в стек заносится 15 задач для нового объекта $worker через метод Worker::stack , а затем они обрабатываются в порядке их внесения. Метод Worker::collect , как показано выше, используется для очистки задач, как только они закончат выполнение. С его помощью внутри цикла while, мы блокируем основной поток, пока не будут завершены все задачи из стека и пока они не будут очищены — до того как мы вызовем Worker::shutdown . Завершение worker -а досрочно (т. е. пока есть еще задачи, которые должны быть выполнены) будет по-прежнему блокировать основной поток до тех пор, пока все задачи не завершат своё выполнение, просто задачи не будут почищены сборщиком мусора (что влечёт за собой утечки памяти).

Класс Worker предоставляет несколько других методов, относящихся к его стеку задач, включая Worker::unstack для удаления последней внесённой задачи и Worker::getStacked для получения количества задач в стеке выполнения. Стек worker -а содержит только задачи, которые должны быть выполнены. Как только задача из стека была выполнена, она удаляется и размещается в отдельном (внутреннем) стеке для сборки мусора (с помощью метода Worker::collect).

Еще один способ переиспользовать поток при выполнении многих задач — это использование пула потоков (через класс Pool). Пул потоков использует группу Worker -ов, чтобы дать возможность выполнять задачи одновременно , в котором фактор параллельности (число потоков пула, с которыми он работает) задается при создании пула.

Давайте адаптируем приведенный выше пример для использования пула worker -ов:

Class Task extends Threaded { private $value; public function __construct(int $i) { $this->value = $i; } public function run() { usleep(250000); echo "Task: {$this->value}\n"; } } $pool = new Pool(4); for ($i = 0; $i submit(new Task($i)); } while ($pool->collect()); $pool->shutdown();

Есть несколько заметных различий при использовании пула, в отличие от воркера. Во-первых, пул не требует запуска вручную, он приступает к выполнению задач, как только они становятся доступными. Во-вторых, мы отправляем задачи в пул, а не укладываем их в стек . Кроме того, класс Pool не наследуется от Threaded , и поэтому он не может быть передан в другие потоки (в отличие от Worker).

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

pthreads и (не)изменяемость

Последний класс, которого мы коснёмся, - Volatile , - новое дополнение к pthreads v3. Понятие неизменяемости стало важной концепцией в pthreads, так как без неё производительность существенно снижается. Поэтому по умолчанию, свойства Threaded -классов, которые сами являются Threaded -объектами, сейчас являются неизменными, и поэтому они не могут быть перезаписаны после их первоначального присвоения. Явная изменяемость для таких свойств сейчас пока предпочтительна, и все еще может быть достигнута с помощью нового класса Volatile .

Давайте взглянем на пример, который продемонстрирует новые ограничения неизменяемости:

Class Task extends Threaded // a Threaded class { public function __construct() { $this->data = new Threaded(); // $this->data is not overwritable, since it is a Threaded property of a Threaded class } } $task = new class(new Task()) extends Thread { // a Threaded class, since Thread extends Threaded public function __construct($tm) { $this->threadedMember = $tm; var_dump($this->threadedMember->data); // object(Threaded)#3 (0) {} $this->threadedMember = new StdClass(); // invalid, since the property is a Threaded member of a Threaded class } };

Threaded -свойства у классов Volatile , с другой стороны, изменяемы:

Class Task extends Volatile { public function __construct() { $this->data = new Threaded(); $this->data = new StdClass(); // valid, since we are in a volatile class } } $task = new class(new Task()) extends Thread { public function __construct($vm) { $this->volatileMember = $vm; var_dump($this->volatileMember->data); // object(stdClass)#4 (0) {} // still invalid, since Volatile extends Threaded, so the property is still a Threaded member of a Threaded class $this->volatileMember = new StdClass(); } };

Мы видим, что класс Volatile переопределяет неизменяемость, навязанную родительским классом Threaded , чтобы предоставить возможность изменять Threaded -свойства (а также unset() -ить).

Есть ещё один предмет обсуждения чтобы раскрыть тему изменяемости и класса Volatile - массивы. В pthreads массивы автоматически приводятся к Volatile -объектам при присвоении к свойству класса Threaded . Это потому что просто небезопасно манипулировать массивом из нескольких контекстов PHP.

Давайте снова взглянем на пример, чтобы лучше понимать некоторые вещи:

$array = ; $task = new class($array) extends Thread { private $data; public function __construct(array $array) { $this->data = $array; } public function run() { $this->data = 4; $this->data = 5; print_r($this->data); } }; $task->start() && $task->join(); /* Вывод: Volatile Object ( => 1 => 2 => 3 => 4 => 5) */

Мы видим, что Volatile -объекты могут быть обработаны так, как если бы они были массивами, т. к. они поддерживают операции с массивами, такие как (как показано выше) оператор подмножеств (). Однако, классы Volatile не поддерживают базовые функции с массивами, такие как array_pop и array_shift . Вместо этого, класс Threaded предоставляет нам подобные операции как встроенные методы.

В качестве демонстрации:

$data = new class extends Volatile { public $a = 1; public $b = 2; public $c = 3; }; var_dump($data); var_dump($data->pop()); var_dump($data->shift()); var_dump($data); /* Вывод: object(class@anonymous)#1 (3) { ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) } int(3) int(1) object(class@anonymous)#1 (1) { ["b"]=> int(2) } */

Другие поддерживаемые операции включают в себя Threaded::chunk и Threaded::merge .

Синхронизация

В последнем разделе этой статьи мы рассмотрим синхронизацию в pthreads. Синхронизация — это метод, позволяющий контролировать доступ к общим ресурсам.

Для примера, давайте реализуем простейший счетчик:

$counter = new class extends Thread { public $i = 0; public function run() { for ($i = 0; $i i; } } }; $counter->start(); for ($i = 0; $i i; } $counter->join(); var_dump($counter->i); // выведет число от 10 до 20

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

Давайте исправим это так, что мы получим правильный вывод 20 , путем добавления синхронизации:

$counter = new class extends Thread { public $i = 0; public function run() { $this->synchronized(function () { for ($i = 0; $i i; } }); } }; $counter->start(); $counter->synchronized(function ($counter) { for ($i = 0; $i i; } }, $counter); $counter->join(); var_dump($counter->i); // int(20)

Синхронизированные блоки кода могут также взаимодействовать друг с другом, используя методы Threaded::wait и Threaded::notify (или Threaded::notifyAll).

Вот поочерёдный инкремент в двух синхронизированных циклах while:

$counter = new class extends Thread { public $cond = 1; public function run() { $this->synchronized(function () { for ($i = 0; $i notify(); if ($this->cond === 1) { $this->cond = 2; $this->wait(); } } }); } }; $counter->start(); $counter->synchronized(function ($counter) { if ($counter->cond !== 2) { $counter->wait(); // wait for the other to start first } for ($i = 10; $i notify(); if ($counter->cond === 2) { $counter->cond = 1; $counter->wait(); } } }, $counter); $counter->join(); /* Вывод: int(0) int(10) int(1) int(11) int(2) int(12) int(3) int(13) int(4) int(14) int(5) int(15) int(6) int(16) int(7) int(17) int(8) int(18) int(9) int(19) */

Вы можете заметить дополнительные условия, которые были размещены вокруг обращения к Threaded::wait . Эти условия имеют решающее значение, поскольку они позволяют синхронизированному колбэку возобновить работу, когда он получил уведомление и указанное условие равно true . Это важно, потому что уведомления могут поступать из других мест, кроме как при вызове Threaded::notify . Таким образом, если вызовы метода Threaded::wait не были заключены в условиях, мы будем выполнять ложные вызовы пробуждения , которые приведут к непредсказуемому поведению кода.

Заключение

Мы рассмотрели пять классов пакета pthreads (Threaded , Thread , Worker , Volatile и Pool), а также как каждый из классов используется. А ещё мы взглянули на новую концепцию неизменяемости в pthreads, сделали краткий обзор поддерживаемых возможностей синхронизации. С этими основами, мы можем теперь приступить к рассмотрению применения pthreads в случаях из реального мира! Это и будет темой нашего следующего поста.

Если вам интересен перевод следующего поста, дайте знать: комментируйте в соц. сетях, плюсуйте и делитесь постом с коллегами и друзьями.

НЕВРОЗ СЕРДЦА (КАРДИОФОБИЯ). Особой формой фобии наряду с синдромом паники является кардиофобия, которую следует описать особо вследствие ее характерной клиники и значительной частоты. В основном она встречается у молодых людей, чаще у мужчин, а также у детей.

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

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

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

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

УСЛОВИЯ ВОЗНИКНОВЕНИЯ. Поводом к первому кардиофобическому приступу чаще бывают острый конфликт и перенапряжение, разлука и разочарование, ситуация одиночества и покинутость, а также переживание в случае сердечной смерти кого-то из близких.

Знание о том, что всегда может случиться сердечная смерть, даже у молодых и здоровых становится тревожащим фактором. Интенсивное употребление кофе и никотина может вызвать к жизни этот процесс. Начало же часто исходит из детства. Преимущественно поражаются избалованные и несамостоятельные дети с выраженной зависимостью от матери, во многом с амбивалентными установками: ожидание любви, с одной стороны, и желание самостоятельности с агрессивными порывами - с другой, с противоречивыми фантазиями привязанности и расставания. Такие установки особенно опасны при обрыве связей, разлуках и разочарованиях. Кардиофобик часто живет в страхе перед разлукой, прежде чем поймет, что он ее хочет и ее же боится. Регулярно возникают совместные проблемы с родителями и конфликты с партнерами.

ЛЕЧЕНИЕ
Если в остром состоянии наличие врача и беседа с ним не вызывают улучшения, показаны транквилизаторы или бета-блокаторы. Как и другие больные неврозами со страхом, многие фобики пытаются проводить самолечение алкоголем; но эффект его недостаточный, а опасность появления зависимости от алкоголя велика. Фармакотерапия - это только подсобное средство, прежде всего в острых случаях, а также начальное эффективное средство.

Решающей же является психотерапия. Чем раньше ее начинают, тем лучше. Изучение причин и конфликтных ситуаций сразу после первых кардиофобических приступов может приостановить последующее фобическое развитие. Позже лечение проводить тяжелее и требуется длительная психотерапия.

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

В сети гуляет довольно много решений для эмуляции многопоточности в php. Чаще всего они основываются на форках, но есть и вариации на тему с использованием curl, proc_open и т.п.

Все встреченные варианты по тем или иным причинам меня не устроили и пришлось написать свое решение.
Набор требований у меня был следующий:

  • Использование форков;
  • Синхронный режим с сохранением интерфейса при отсутствии необходимых расширений;
  • Многократное использование дочерних процессов;
  • Полноценный обмен данными между процессами. Т.е. запуск с аргументами и получение результата по завершении;
  • Возможность обмена событиями между дочерним процессом-«потоком» и основным процессом во время работы;
  • Работа с пулом потоков с сохранением многократного использования, передачи аргументов и получения результатов;
  • Обработка ошибок выполнения;
  • Таймауты на выполнение работы, ожидание работы потоком, инициализацию;
  • Максимум производительности;
В результате получилась библиотека AzaThread (старое название - CThread).

Для нетерпеливых сразу ссылка на исходники:
github.com/Anizoptera/AzaThread

Описание

AzaThread предоставляет простой интерфейс для создания классов-потоков. Которые на самом деле используют отдельные процессы для асинхронной работы, но вас это не должно волновать. Вы можете посылать события из потока, возвращать результаты, использовать один поток множество раз передавая ему аргументы запуска или создать пул из 16 потоков разгребающих ваши задачи как горячие пирожки не обращая никакого внимания на то, что работа происходит в разных процессах.

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

Для полноценной работы требуются следующие расширения: libevent , posix и pcntl .

Библиотека использует LibEvent и парные сокеты для общения между процессами. Поддерживает 5 вариантов передачи данных (аргументов, результатов и данных событий)!

Варианты привожу сразу с данными производительности. Тестировалось с пулом из восьми потоков на Intel Core i7 2600K 3.40 Ghz (Ubuntu 11.04 на виртуалке VMware). Приведены средние результаты за 10 повторов теста в jps (jobs per second - кол-во задач просто получающих аргументы и отдающих данные в секунду).

Автоматически выбирается расширение для работы с сокетами. Если доступно, то используется расширение sockets , что дает улучшение производительности. В ином случае задействуется stream .

В дочернем процессе слушаются все доступные сигналы. По умолчанию на все из них (кроме SIGWINCH и SIGINFO) следует завершение работы. Но это легко можно переопределить создав в классе потока метод с именем сигнала. Например sigWinch .

В родительском процессе по умолчанию тоже перхватываются все сигналы. Это можно изменить выставив у класса параметр listenMasterSignals в false. В этом случае обрабатываться будет только SIGCHLD. Свои собственные обработчики можно легко добавить создав статический метод с названием m< имя сигнала > . Например mSigTerm .

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

Дочерний процесс время от времени проверяет существование родительского. Если он вдруг помрет, то дочерний автоматический завершится.

Все ресурсы используемые потоком или пулом потоков автоматически очищаются при вызове деструктора. Но их можно очистить принудительно если вызывать метод cleanup . В этом случае поток/пул больше нельзя использовать.

При стандартных настройках поток инициализируется заранее, сразу при создании класса. Если установить параметр prefork в false, то форк будет происходить только в момент запуска задачи.

Вообще настраиваемых параметров довольно много. Смена имени дочернего процесса после форка (параметр pName конструктора), таймаут на время выполнения задачи (timeoutWork ), таймаут на максимальное время ожидания задач дочерним процессом (timeoutMaxWait ), таймаут на время пре-инициализации (timeoutInit ), размеры буферов для чтения сокетов (pipeReadSize , pipeMasterReadSize ).
Можно отключить режим мультизадачности для потоков (multitask ). В этом случае каждый раз по завершении задачи дочерний процесс будет умирать и форкаться заново для следующего запуска. Это заметно уменьшит производительность.

Код покрыт тестами и подробно документирован, примеры использования можно посмотреть и запустить в файле example.php .
Более сложные примеры с обработкой ошибок можно увидеть в коде юнит теста.

Есть режим отладки в котором выводится очень подробная информация о том, что именно и где творится.

Примеры использования

Основная фича - максимальная простота. Если вы хотите просто запустить что либо в отдельном «потоке» достаточно следующего кода:
class ExampleThread extends Thread { protected function process() { // Some work here } } $thread = new ExampleThread(); $thread->wait()->run();
Если есть все необходимое для полноценной работы, то задача будет выполнена асинхронно. Если нет, то все будет по прежнему работать, но в синхронном режиме.

С передачей параметра и получением результата код будет выглядеть лишь чуть чуть сложнее:
class ExampleThread extends Thread { protected function process() { return $this->getParam(0); } } $thread = new ExampleThread(); $thread->wait()->run(123); $result = $thread->wait()->getResult();

Аналогично легким мановением руки добавляем обработку событий из потока:
class ExampleThread extends Thread { const EV_PROCESS = "process"; protected function process() { $events = $this->getParam(0); for ($i = 0; $i < $events; $i++) { $event_data = $i; $this->trigger(self::EV_PROCESS, $event_data); } } } // Дополнительный аргумент. $additionalArgument = 123; $thread->bind(ExampleThread::EV_PROCESS, function($event_name, $event_data, $additional_arg) { // обработка события }, $additionalArgument); $events = 10; // число событий, которое сгенерирует поток // Чтобы не вызывать вручную ожидание потока перед первым вызовом, // можно переопределить свойство preforkWait в TRUE в классе-наследнике $thread->wait(); $thread = new ExampleThread(); $thread->run($events)->wait();

И наконец использование пула из восьми потоков с обработкой ошибок выполнения:
$threads = 8 // Число потоков $pool = new ThreadPool("ExampleThread", $threads); $num = 25; // Количество задач $left = $num; // Количество оставшихся задач do { // Если в пуле есть свободные потоки // И у нас остались задачи для выполнения while ($pool->hasWaiting() && $left > 0) { // При запуске получаем id потока $threadId = $pool->run(); $left--; } if ($results = $pool->wait($failed)) { foreach ($results as $threadId => $result) { // Успешно выполненная задача // Результат можно идентифицировать // по id потока ($threadId) $num--; } } if ($failed) { // Обработка ошибок выполнения. // Работа считается завершенной неуспешно // если дочерний процесс умер во время выполнения или // истек таймаут на выполнение задачи foreach ($failed as $threadId) { $left++; } } } while ($num > 0); // Завершаем все дочерние процессы. Очищаем ресурсы используемые пулом. $pool->cleanup();

Результаты тестирования производительности

Тесты запускал на двух машинах с Ubuntu 11.04.
Первая - Intel Core i3 540 3.07 Ghz
Вторая - Intel Core i7 2600K 3.40 Ghz (убунту стоит на VMware виртуалке)

Результаты привожу просто чтобы можно было оценить рост производительности.
Опять же это средние результаты за серию из 10 повторов теста в jps (jobs per second - кол-во задач в секунду).

В качестве задачи потоки выполняют следующую фигню:
for ($i = 0; $i < 1000; $i++) { $r = mt_rand(0, PHP_INT_MAX) * mt_rand(0, PHP_INT_MAX); }
Первый результат указан для синхронного режима работы (без форков).
18 и 20 потоков на первой конфигурации я не пробовал, так как уже для 12 началось падение производительности.

Число потоков Первая конфигурация Вторая
0 553 763
1 330 669
2 580 1254
4 1015 2188
8 1040 2618
10 1027 2719
12 970 2739
16 958 2904
18 - 2830
20 - 2730

То бишь производительность поднимается в 2-4 и более раза в зависимости от процессора!

Код, выполняющий серию тестов с нужными параметрами, лежит в файле examples/speed_test.php . Так что вы легко можете протестировать производительность и выбрать оптимальное число потоков у себя.



Понравилась статья? Поделитесь ей