Розробка багатозадачних додатків на PHP V5 (исходники), Різне, Програмування, статті

PHP не підтримує обробку потоків. Незважаючи на це, і на противагу думці більшості PHP-розробників, з якими я спілкувався, PHP-додатки можуть бути багатозадачними. Почнемо із з’ясування того, що “багатозадачність” і “потоковість” означають для PHP-програмування.


Різноманіття паралелізму


Спочатку відкладемо в сторону випадки, що лежать поза русла головної теми. У PHP складні взаємини з багатозадачністю або паралелізмом. На верхньому рівні PHP постійно залучений в багатозадачність – стандартні установки PHP на сервері (наприклад, модуль Apache) використовуються багатозадачним способом. Тобто кілька клієнтських додатків (Web-браузерів) можуть одночасно запросити одну і ту ж PHP-сторінку, і Web-сервер поверне її всім більш-менш одночасно.


Одна Web-сторінка не блокує передачу інший, хоча вони можуть трохи заважати один одному при роботі з такими обмеженими ресурсами як пам’ять сервера або пропускна здатність мережі. Таким чином, системне вимога забезпечення паралелізму може цілком допускати засновані на PHP рішення. У термінах реалізації PHP покладає на Web-сервер відповідальність за паралелізм.


Паралелізм на стороні клієнта під назвою Ajax теж привернув увагу розробників в останні кілька років. Хоча значення Ajax стало кілька неясним, одним з аспектів цієї технології є те, що браузер може одночасно виконувати обчислення і залишатися чутливим до таких дій користувача, як вибір пунктів меню. Це дійсно почасти багатозадачність. Закодований на PHP Ajax робить це, але без будь-якого спеціального участі PHP; інтегровані середовища Ajax для інших мов працюють точно також.


Третім прикладом паралелізму, який тільки поверхнево зачіпає PHP, є PHP / TK. PHP / TK – це розширення PHP, що надає переносяться зв’язування графічного інтерфейсу користувача (Graphical User Interface – GUI) ядру PHP. PHP / TK дозволяє створювати настільні GUI-додатка, написані на PHP. Його засновані на подіях аспекти моделюють форму паралелізму, яку легко вивчити, і вона менше схильна до помилок, ніж робота з потоками. Знову ж таки, паралелізм “успадкований” від додаткової технології, а не є фундаментальною функціональністю PHP.


Було кілька експериментів по додаванню підтримки поточности в сам PHP. Наскільки я знаю, ні один не був вдалим. Однак орієнтовані на події інтегровані середовища Ajax і PHP / TK показують, що події можуть ще краще висловити паралелізм для PHP, ніж це роблять потоки. PHP V5 доводить це.


PHP V5 пропонує stream_select ()


У стандартному PHP V4 і більш ранніх версіях вся робота PHP-додатки повинна виконуватися послідовно. Якщо програмі потрібно витягти ціну товару з двох комерційних сайтів, наприклад, вона запитує перший ціну, чекає отримання відповіді, запитує другий ціну і чекає знову.


Що, якби програма могла виконувати декілька завдань одночасно? Вона завершувалася б лише за частину того часу, який необхідно при послідовній роботі.


Перший приклад


Нова функція stream_select, Разом з кількома своїми друзями, надає цю можливість. Розглянемо наступний приклад:


Лістинг 1. Одночасний запит декількох HTTP-сторінок





<?php
echo “Program starts at “. date(“h:i:s”) . “.
“;
$timeout=10;
$result=array();
$sockets=array();
$convenient_read_block=8192;
/ * Виконати одночасно всі запити; нічого не блокується. * /
$delay=15;
$id=0;
while ($delay > 0) {
$s=stream_socket_client(“phaseit.net:80″, $errno,
$errstr, $timeout,
STREAM_CLIENT_ASYNC_CONNECT/STREAM_CLIENT_CONNECT);
if ($s) {
$sockets[$id++]=$s;
$http_message=”GET /demonstration/delay?delay=” .
$delay . ” HTTP/1.0
Host: phaseit.net
“;
fwrite($s, $http_message);
} else {
echo “Stream ” . $id . ” failed to open correctly.”;
}
$delay -= 3;
}

while (count($sockets)) {
$read=$sockets;
stream_select($read, $w=null, $e=null, $timeout);
if (count($read)) { / * Stream_select зазвичай перемішує $ read, тому ми повинні обчислити, з якого сокета виконується читання. * /
foreach ($read as $r) {
$id=array_search($r, $sockets);
$data=fread($r, $convenient_read_block); / * Сокет можна прочитати або тому що він має дані для читання, АБО тому що він в змозі EOF. * /
if (strlen($data) == 0) {
echo “Stream ” . $id . ” closes at ” . date(“h:i:s”) . “.
“;
fclose($r);
unset($sockets[$id]);
} else {
$result[$id] .= $data;
}
}
} else { / * Таймаут означає, що * все * потоки не дочекалися отримання відповіді. * /
echo “Time-out!
“;
break;
}
}
?>


Якщо виконати цю програму, відобразиться приблизно наступна інформація:


Лістинг 2. Типова інформація, що виводиться програмою з лістингу 1




     Program starts at 02:38:50.
Stream 4 closes at 02:38:53.
Stream 3 closes at 02:38:56.
Stream 2 closes at 02:38:59.
Stream 1 closes at 02:39:02.
Stream 0 closes at 02:39:05.

Важливо розуміти, що тут відбувається. На високому рівні перша програма виконує кілька HTTP-запитів і отримує сторінки, які передає їй Web-сервер. Хоча реальна програма, напевно, запитував б кілька різних Web-серверів (можливо google.com, yahoo.com, ask.com і т.д.), цей приклад передає всі запити на наш корпоративний сервер на Phaseit.net просто заради зменшення складності.


Запитані Web-сторінки повертають результати після змінної затримки, показаної нижче. Якби програма виконувала запити послідовно, для її завершення знадобилося б близько 15 +12 +9 +6 +3 (45) секунд. Як показано в лістингу 2, насправді вона завершується за 15 секунд. Потроєння продуктивності – це відмінний результат.


Таке стало можливим завдяки stream_select – Нової функції в PHP V5. Запити ініціюються звичайним способом – відкриттям декількох stream_socket_clients і написанням GET до кожного з них, що відповідає http://phaseit.net/demonstration/delay?delay=$DELAY. При запиті цього URL в браузері ви повинні побачити:






	  Starting at Thu Apr 12 15:05:01 UTC 2007.
Stopping at Thu Apr 12 15:05:05 UTC 2007.
4 second delay.

Сервер затримки реалізований на CGI, як показано нижче.


Лістинг 3. Реалізація сервера затримки




     #!/bin/sh
echo “Content-type: text/html
<HTML> <HEAD></HEAD> <BODY>”
echo “Starting at `date`.”
RR=`echo $REQUEST_URI / sed -e “s/.*?//”`
DELAY=`echo $RR / sed -e “s/delay=//”`
sleep $DELAY
echo “<br>Stopping at `date`.”
echo “<br>$DELAY second delay.</body></html>”

Хоча конкретна реалізація в лістингу 3 призначена для UNIX ®, майже всі сценарії даної статті з тим же успіхом застосовуються для установок PHP в Windows ® (особливо після Windows 98) або UNIX. Зокрема, з лістингом 1 можна працювати на будь-якій операційній системі. Linux ® і Mac OS X є варіаціями UNIX, і весь наведений тут код буде працювати в обох системах.


Запити до сервера затримки виконуються в наступному порядку:


Лістинг 4. Послідовність виконання процесу




     delay=15
delay=12
delay= 9
delay= 6
delay= 3

Метою stream_select є якомога швидке отримання результатів. В даному випадку порядок затримок протилежний порядку, в якому були зроблені запити. Через 3 секунди перша сторінка готова для читання. Ця частина програми є звичайним PHP-кодом – в даному випадку з fread. Також як і в іншій PHP-програмі читання могло б здійснюватися за допомогою fgets.


Обробка триває таким же чином. Програма блокується в stream_select, Поки не будуть готові дані. Вирішальним є те, що вона починає читання, як тільки-яке з’єднання буде мати дані, в якому порядку. Саме так програма реалізує багатозадачність або паралельну обробку результатів декількох запитів.


Зверніть увагу на те, що при цьому немає додаткового навантаження на CPU хост-комп’ютера. Немає нічого незвичайного в тому, що мережеві програми, що виконують fread таким способом, незабаром починають використовувати 100% потужності CPU. Тут не цей випадок, оскільки stream_select має бажані властивості і відповідає негайно, як тільки якесь читання стає можливим, але при цьому мінімальним чином завантажує CPU в режимі очікування між операціями читання.


Що потрібно знати про stream_select ()


Подібне засноване на подіях програмування не є елементарним завданням. Хоча лістинг 1 і зменшений до самих основних моментів, будь кодування, що базується на зворотних виклики або координації (Що є необхідним в багатозадачних додатках) буде менш звичним в порівнянні з простою процедурної послідовністю. В даному випадку найбільша трудність полягає в масиві $read. Зверніть увагу на те, що це посилання; stream_select повертає важливу інформацію шляхом зміни вмісту $read. Так само як покажчики мають репутацію постійного джерела помилок в C, посилання, мабуть, є тією частиною PHP, яка представляє найбільшу складність для програмістів.


Таку методику запитів можна використовувати з будь-якого числа зовнішніх Web-сайтів, переконався в тому, що програма буде отримувати кожен результат якомога швидше, не чекаючи інших запитів. Фактично, дана методика коректно працює з будь-яким TCP / IP-з’єднанням, а не тільки з Web (порт 80), тобто в принципі ви можете управляти витяганням LDAP-даних, передачею SMTP, SOAP-запитами і т.д.


Але це не все. PHP V5 управляє різними сполуками як “потоками” (stream), а не простими сокетами. Бібліотека PHP Client URL (CURL) підтримує HTTPS-сертифікати, витікаючу FTP-завантаження, куки і багато інше (CURL дозволяє PHP-додатків використовувати різні протоколи для з’єднання з серверами). Оскільки CURL надає інтерфейс stream, з точки зору програми з’єднання прозоро. В наступному розділі розповідається, як stream_select мультиплексирует навіть локальні обчислення.


Для stream_select існує кілька застережень. Ця функція не документована, тому не розглядається навіть у нових книгах по PHP. Кілька прикладів коду, доступні в Web, просто не працюють або не зрозумілі. Другий і третій аргументи stream_select, Що керують каналами write і exception, Відповідними каналами read в лістингу 1, майже завжди повинні бути рівні null. За деякими винятками вибір цих каналів є помилкою. Якщо ви не маєте достатнього досвіду, використовуйте тільки добре описані варіанти.


Крім того, в stream_select, По всій видимості, є помилки, по крайней мере, в PHP V5.1.2. Найбільш істотним є те, що значенням повернення функції не можна довіряти. Хоча я ще не налагодив реалізацію, мій досвід показав, що безпечно тестувати count($read) так, як в лістингу 1, але це не відноситься до значення повернення самої stream_select, Незважаючи на офіційну документацію.


Локальний паралелізм PHP


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


Що якщо уповільнення відбувається ближче? Чи є спосіб прискорити отримання результатів в PHP при локальній обробці? Є декілька. Мабуть, вони ще менш відомі, ніж орієнтований на сокети підхід в лістингу 1. Цьому є кілька причин, в тому числі:



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


В світі PHP-обчислень такі приклади є рідкістю. Однак оскільки я більше ніде не знайшов точного опису, хочу навести тут приклад подібного прискорення.


Лістинг 5. Реалізація сервера затримок





<?php
echo “Program starts at “. date(“h:i:s”) . “.
“;

$timeout=10;
$streams=array();
$handles=array();
/ * Спочатку запустити програму з затримкою в 3 секунди, потім ту, яка повертає результат після однієї секунди. * /
$delay=3;
for ($id=0; $id <= 1; $id++) {
$error_log=”/tmp/error” . $id . “.txt”
$descriptorspec=array(
0 => array(“pipe”, “r”),
1 => array(“pipe”, “w”),
2 => array(“file”, $error_log, “w”)
);
$cmd=”sleep ” . $delay . “; echo “Finished with delay of ” .
$delay . “”.”;
$handles[$id]=proc_open($cmd, $descriptorspec, $pipes);
$streams[$id]=$pipes[1];
$all_pipes[$id]=$pipes;
$delay -= 2;
}

while (count($streams)) {
$read=$streams;
stream_select($read, $w=null, $e=null, $timeout);
foreach ($read as $r) {
$id=array_search($r, $streams);
echo stream_get_contents($all_pipes[$id][1]);
if (feof($r)) {
fclose($all_pipes[$id][0]);
fclose($all_pipes[$id][1]);
$return_value=proc_close($handles[$id]);
unset($streams[$id]);
}
}
}
?>


Дана програма виведе на екран наступну інформацію:






     Program starts at 10:28:41.
Finished with delay of 1.
Finished with delay of 3.

Сенс полягає в тому, що PHP запустив два незалежних субпроцесів, отримав дані від першого, а потім від другого, хоча останній стартував раніше. Якщо хост-комп’ютер є багатопроцесорним, а операційна система коректно настроєна, вона сама піклується про призначення різних субпрограмм різних процесорам. Це один із способів використання переваг багатопроцесорних машин в PHP.


Резюме


PHP підтримує багатозадачність. PHP не підтримує обробку потоків так, як це роблять інші мови програмування, наприклад Java або C + +, але наведені вище приклади показали, що PHP має більш високий потенціал для прискорення роботи, ніж багато хто собі уявляє.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*