Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Асинхронный PHP

Асинхронный PHP

* Разбираемся в блокирующих и неблокирующих операциях в PHP.
* Структура Event Loop и асинхронных примитивов, таких как Promise изнутри.
* Генераторы как способ управления потоком исполнения программы.
* Современные подходы реализации кооперативной многозадачности в PHP.
* Что нас ждет в PHP 8.

Avatar for zloyuser

zloyuser

May 17, 2019
Tweet

More Decks by zloyuser

Other Decks in Programming

Transcript

  1. Теория Асинхронность Способность программной системы не блокировать поток выполнения. Асинхронная

    операция Операция не блокирующая поток выполнения программы до своего завершения.
  2. Блокирующие операции public function update(User $user) { try { $sql

    = 'UPDATE users SET ...'; return $this->connection->execute($sql, $user->data()); } catch (\PDOException $error) { log($error->getMessage()); } return 0; }
  3. Блокирующие операции public function update(User $user) { try { $sql

    = 'UPDATE users SET ...'; return $this->connection->execute($sql, $user->data()); } catch (\PDOException $error) { log($error->getMessage()); } return 0; }
  4. public function update(User $user) { try { $sql = 'UPDATE

    users SET ...'; return $this->connection->execute($sql, $user->data()); } catch (\PDOException $error) { log($error->getMessage()); } return 0; } Блокирующие операции
  5. Блокирующие операции I/O • Файловая система: fwrite, file_get_contents, etc. •

    Базы Данных: \PDOConnection, \RedisClient, etc. • Процессы: exec, system, proc_open, etc.
  6. Блокирующие операции I/O • Файловая система: fwrite, file_get_contents, etc. •

    Базы Данных: \PDOConnection, \RedisClient, etc. • Процессы: exec, system, proc_open, etc. • stdin/stdout: readline, echo, print, etc.
  7. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { $socket = stream_socket_client('127.0.0.1:3306', ...); stream_set_blocking($socket, false); $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); }
  8. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { $socket = stream_socket_client('127.0.0.1:3306', ...); stream_set_blocking($socket, false); $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); }
  9. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { $socket = stream_socket_client('127.0.0.1:3306', ...); stream_set_blocking($socket, false); $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); }
  10. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { $socket = stream_socket_client('127.0.0.1:3306', ...); stream_set_blocking($socket, false); $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); }
  11. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { $socket = stream_socket_client('127.0.0.1:3306', ...); stream_set_blocking($socket, false); $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); }
  12. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { ... $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); return ??? }
  13. interface Promise { const STATUS_PENDING = 0, STATUS_RESOLVED = 1,

    STATUS_REJECTED = 2 ; public function onResolve(callable $callback); public function onReject(callable $callback); public function resolve($data); public function reject(\Throwable $error); }
  14. Асинхронный клиент public function execAsync(string $query, array $params = [])

    { $deferred = new Deferred; $socket = stream_socket_client('127.0.0.1:3306', ...); stream_set_blocking($socket, false); $data = $this->packBinarySQL($query, $params); socket_write($socket, $data, strlen($data)); return $deferred->promise(); }
  15. Асинхронный клиент $promise = $this->execAsync($sql, $user->data()); $promise->onResolve(function (int $rows) {

    echo "Affected rows: {$rows}"; }); $promise->onReject(function (\Throwable $error) { log($error->getMessage()); });
  16. public static function run() { while (true) { stream_select($readSockets, $writeSockets,

    null, 0); foreach ($readSockets as $i => $socket) { call_user_func(self::readCallbacks[$i], $socket); } // Do same for write sockets } }
  17. public function execAsync(string $query, array $params = []) { $deferred

    = new Deferred; ... Loop::onReadable($socket, function ($socket) use ($deferred) { $deferred->resolve(socket_read($socket)); }); return $deferred->promise(); }
  18. $connection = (new ConnectionFactory)->createLazyConnection(); $promise = $connection->query('UPDATE users SET ...');

    $promise->then( function (QueryResult $command) { echo count($command->resultRows) . ' row(s) in set.'; }, function (Exception $error) { echo 'Error: ' . $error->getMessage(); });
  19. $connection = (new ConnectionFactory)->createLazyConnection(); $promise = $connection->query('UPDATE users SET ...');

    $promise->then( function (QueryResult $command) { echo count($command->resultRows) . ' row(s) in set.'; }, function (Exception $error) { echo 'Error: ' . $error->getMessage(); });
  20. $connection = (new ConnectionFactory)->createLazyConnection(); $promise = $connection->query('UPDATE users SET ...');

    $promise->then( function (QueryResult $command) { echo count($command->resultRows) . ' row(s) in set.'; }, function (Exception $error) { echo 'Error: ' . $error->getMessage(); });
  21. $connection = (new ConnectionFactory)->createLazyConnection(); $promise = $connection->query('UPDATE users SET ...');

    $promise->then( function (QueryResult $command) { echo count($command->resultRows) . ' row(s) in set.'; }, function (Exception $error) { echo 'Error: ' . $error->getMessage(); });
  22. $connection = (new ConnectionFactory)->createLazyConnection(); $promise = $connection->query('UPDATE users SET ...');

    $promise->then( function (QueryResult $command) { echo count($command->resultRows) . ' row(s) in set.'; }, function (Exception $error) { echo 'Error: ' . $error->getMessage(); });
  23. $promise ->then(function ($data) { return new Promise(...); }) ->then(function ($data)

    { ... }, function ($error) { log($error); }) ... для корректного подключения к
  24. $pool = Mysql\pool("host=127.0.0.1 port=3306 db=test"); try { $result = yield

    $pool->query("UPDATE users SET ..."); echo $result->affectedRows . ' row(s) in set.'; } catch (\Throwable $error) { echo 'Error: ' . $error->getMessage(); }
  25. $pool = Mysql\pool("host=127.0.0.1 port=3306 db=test"); try { $result = yield

    $pool->query("UPDATE users SET ..."); echo $result->affectedRows . ' row(s) in set.'; } catch (\Throwable $error) { echo 'Error: ' . $error->getMessage(); }
  26. Генераторы $generator = gen(1); // instanceof \Iterator foreach ($generator as

    $value) { echo $value; } while ($generator->valid()) { echo $generator->current(); $generator->next(); }
  27. Генераторы function gen($counter) { yield $counter++; echo "A"; yield $counter;

    echo "B"; yield ++$counter; } echo $generator->current(); 1
  28. Генераторы function gen($counter) { yield $counter++; echo "A"; yield $counter;

    echo "B"; yield ++$counter; } echo $generator->current(); 1A2
  29. Генераторы function gen($counter) { yield $counter++; echo "A"; yield $counter;

    echo "B"; yield ++$counter; } echo $generator->current(); 1A2B3
  30. function printer() { while (true) { echo yield; } }

    $print = printer(); $print->send('Hello'); $print->send(' PHPRussia'); $print->send(' 2019!'); Корутины
  31. function printer() { try { echo yield; } catch (\Throwable

    $e) { echo $e->getMessage(); } } printer()->throw(new \Exception('Ooops...')); Корутины
  32. Генераторы и class Generator { public function send($data); public function

    throw(\Throwable $error); } class Promise { public function resolve($data); public function reject(\Throwable $error); }
  33. Генераторы и function recoil(\Generator $generator) { $promise = $generator->current(); $promise->onResolve(function($data)

    use ($generator) { $generator->send($data); recoil($generator); }; $promise->onReject(function ($error) use ($generator) { $generator->throw($error); recoil($generator); }); }
  34. Генераторы и function recoil(\Generator $generator) { $promise = $generator->current(); $promise->onResolve(function($data)

    use ($generator) { $generator->send($data); recoil($generator); }; $promise->onReject(function ($error) use ($generator) { $generator->throw($error); recoil($generator); }); }
  35. Генераторы и function recoil(\Generator $generator) { $promise = $generator->current(); $promise->onResolve(function($data)

    use ($generator) { $generator->send($data); recoil($generator); }; $promise->onReject(function ($error) use ($generator) { $generator->throw($error); recoil($generator); }); }
  36. Генераторы и function recoil(\Generator $generator) { $promise = $generator->current(); $promise->onResolve(function($data)

    use ($generator) { $generator->send($data); recoil($generator); }; $promise->onReject(function ($error) use ($generator) { $generator->throw($error); recoil($generator); }); }
  37. Тип многозадачности, при котором следующая задача выполняется только после того,

    как текущая задача явно объявит себя готовой отдать процессорное время другим задачам. Кооперативная многозадачность
  38. Асинхронный сервер Loop::run(function () { $app = new Application(); $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')]; $server = new Server($sockets, new CallableRequestHandler( function (Request $request) use ($app) { $response = yield $app->dispatch($request); return new Response(Status::OK, [], $response); }) ); yield $server->start(); });
  39. Асинхронный сервер Loop::run(function () { $app = new Application(); $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')]; $server = new Server($sockets, new CallableRequestHandler( function (Request $request) use ($app) { $response = yield $app->dispatch($request); return new Response(Status::OK, [], $response); }) ); yield $server->start(); });
  40. Асинхронный сервер Loop::run(function () { $app = new Application(); $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')]; $server = new Server($sockets, new CallableRequestHandler( function (Request $request) use ($app) { $response = yield $app->dispatch($request); return new Response(Status::OK, [], $response); }) ); yield $server->start(); });
  41. Асинхронный сервер Loop::run(function () { $app = new Application(); $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80'), Socket\listen('0.0.0.0:8080')]; $server = new Server($sockets, new CallableRequestHandler( function (Request $request) use ($app) { $response = yield $app->dispatch($request); return new Response(Status::OK, [], $response); }) ); yield $server->start(); });
  42. Асинхронный сервер Loop::run(function () { $app = new Application(); $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')]; $server = new Server($sockets, new CallableRequestHandler( function (Request $request) use ($app) { $response = yield $app->dispatch($request); return new Response(Status::OK, [], $response); }) ); yield $server->start(); });
  43. Асинхронный сервер Loop::run(function () { $app = new Application(); $app->bootstrap();

    $sockets = [Socket\listen('0.0.0.0:80')]; $server = new Server($sockets, new CallableRequestHandler( function (Request $request) use ($app) { $response = yield $app->dispatch($request); return new Response(Status::OK, [], $response); }) ); yield $server->start(); });
  44. Проблемы • Отсутствие стандартов ◦ Promise A+, ReactPHP, AMPHP •

    Утечки памяти ◦ xdebug, strace • Блокирующие операции ◦ kelunik/loop-block
  45. Проблемы • Отсутствие стандартов ◦ Promise A+, ReactPHP, AMPHP •

    Утечки памяти ◦ xdebug, strace • Блокирующие операции ◦ kelunik/loop-block • Поддержка библиотек ◦ Драйверы БД: Cassandra, InfluxDB, ClickHouse
  46. Проблемы Указание типа class UserRepository { public function find(int $id):

    \Generator { $data = yield $this->db->query('...', $id); return User::fill($data); } }
  47. Проблемы Указание типа class UserRepository { public function find(int $id):

    iterable { $data = yield $this->db->query('...', $id); return User::fill($data); } }
  48. class UserRepository { public function find(int $id): Promise<User> { $data

    = await $this->db->query('...', $id); return User::fill($data); } }
  49. class UserRepository { public function find(int $id): Promise<User> { $data

    = yield $this->db->query('...', $id); return User::fill($data); } }