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

Asynchronous Programming Fundamentals

Asynchronous Programming Fundamentals

Samantha Quiñones

October 08, 2016
Tweet

More Decks by Samantha Quiñones

Other Decks in Technology

Transcript

  1. Samantha Quiñones Principal Software Engineer Code and Community Champion AOL

    Inc. @ieatkillerbees @squinones ɂ http://squinones.github.io ɔ https://joind.in/talk/3a1de
  2. Synchronous Operations <?php declare(strict_types = 1); //process command line input

    $filename = $argv[1] ?? "data.txt"; $fd = fopen($filename, 'r+'); $data = fread($fd, filesize($filename)); fclose($fd); echo $data; assign $filename open $filename read file write to stdout
  3. What is Asynchrony? • Events that occur outside the main

    program flow • Concurrency • Non-blocking I/O • Parallelism Procedure Procedure Procedure Async Event Async Event
  4. Asynchronous Events <?php declare(ticks = 1); pcntl_signal(SIGUSR1, function (int $signal)

    { echo "Caught signal " . $signal . PHP_EOL; }); echo "Running as PID " . posix_getpid() . PHP_EOL; while (true) { echo "Waiting for signals..." . PHP_EOL; sleep(3); }
  5. Asynchronous Events <?php $command = "du -sk /var/log/* | sort

    -n | tail -1 2>&1"; echo shell_exec($command); Primary Process Secondary Process
  6. Asynchronous programming describes a set of techniques designed to parallelize

    work and minimize idleness. Operation 1 Operation 2 Operation 3 Operation 1 Operation 2 Operation 3 Op1 Op 2 Op 3 Op 2 Op1 Op 3 Op1
  7. Client sends a payload { requests: [ posts.query({“status”: “published”}), slideshows.query({“status”:

    “published”}) ] } API executes procedures asynchronously When procedures are all done, API returns { responses: [ posts.query: { … }, slideshows.query: { … }) ] }
  8. What about HTTP Requests? {% set request1 = http('http://example.com/service-foo') %}

    {% set request2 = http('http://example.com/service-bar') %} {% set request3 = http('http://example.com/service-baz') %} <table> <tr><th>Foo<th><td>{{ request1.body.field }}</td></tr> <tr><th>Bar<th><td>{{ request2.body.field }}</td></tr> <tr><th>Baz<th><td>{{ request3.body.field }}</td></tr> </table> 233ms 318ms 188ms 739ms
  9. $pool = new Pool($client, $requests, [ 'concurrency' => 10, 'fulfilled'

    => $complete, 'rejected' => $complete ]); $pool->promise()->wait(); For each request, we create a Promise. A Promise represents an eventual result. We wait for all Promises to be Fulfilled or Rejected Gather requests Execute request 1 Execute request 2 Execute request 3 Execute request 4 Return responses
  10. <?php require __DIR__ . '/vendor/autoload.php'; use GuzzleHttp\Promise\Promise; $promise = new

    Promise(); $promise->then( // onFulfilled function ($value) { echo "Promise fulfilled successfully! - " . $value; }, // onRejected function ($reason) { echo "Promise rejected! :( - " . $reason; } ); $promise->resolve("OpenWest");
  11. The Point of Promises <?php function doTheThing(Thing $thing): string {

    $done = $this->do(); if (!$done) { throw new NotDoneException("Didn't do the thing! :("); } return $done; } try { $doneThing = doTheThing(new Thing()); echo $doneThing; } catch (NotDoneException $exc) { echo $exc->getMessage(); }
  12. The Point of Promises <?php $delayed = new Delayed(); $delayed

    ->then( function (Thing $thing): string { $done = $this->do(); if (!$done) { throw new NotDoneException("Didn't do the thing! :("); } return $done; } ) ->capture( function (NotDoneException $e) { echo $e->getMessage(); } ) ->done( function (string $value) { echo $value; } ) ;
  13. Asynchronous Functions • Can’t return values because there’s nothing to

    receive them • Can’t throw exceptions because there’s nothing to catch them • Promises allow us to compose the results of async functions and handle errors from async functions. • Promises are a paradigmatic translation layer!
  14. Blocking I/O • When we read from or write to

    disk • When we read from or write to a network connection • Communicating with an external service (DB, API, etc)
  15. Format Time (s) Equivalent Distance Equivalent Time 1 CPU Cycle

    0.3 ns 1 m (1 step) 1 second L1 Cache 0.9 ns 3 m (3 steps) 3 seconds Main Memory 120 ns 360 m (US Capitol Grounds) 6 minutes SSD 50 µs 170 km (Prishtina) 2 days HDD 5 ms 13,000 km (Falkland Islands) 5 months
  16. <?php declare(strict_types = 1); $filename = $argv[1] ?? "data/data.txt"; $fd

    = fsockopen('localhost', 4000); $data = fread($fd, 512); fclose($fd); echo $data;
  17. <?php declare(strict_types = 1); //process command line input $filename =

    $argv[1] ?? "data/data.txt"; $fd = fsockopen('localhost', 4000); stream_set_blocking($fd, false); // enable non-blocking calls $data = fread($fd, 512); fclose($fd); echo $data;
  18. <?php declare(strict_types = 1); //process command line input $filename =

    $argv[1] ?? "data/data.txt"; $fd = fsockopen('localhost', 4000); stream_set_blocking($fd, false); // enable non-blocking calls $data = ""; while (strlen($data) < 512) { $read = [$fd]; $write = []; $except = []; if (stream_select($read, $write, $except, 0) > 0) { $chunk = ""; do { $chunk = fread($fd, 512 - strlen($data)); $data .= $chunk; } while ($chunk !== "" && strlen($data) < 512); } } fclose($fd); echo $data;
  19. Event Loops in PHP • ReactPHP (mature, supports stream_select &

    libevent) • Icicle (newer, supports stream_select, libevent, & libuv)
  20. Non-Blocking Server <?php require __DIR__.'/vendor/autoload.php'; $loop = React\EventLoop\Factory::create(); $socket =

    new React\Socket\Server($loop); $socket->on('connection', function ($conn) { usleep(500000); $conn->write(str_repeat('a', 1024)); $conn->end(); }); echo "Socket server listening on port 4000.\n"; echo "You can connect to it by running: telnet localhost 4000\n"; $socket->listen(4000); $loop->run();
  21. Events • File descriptor (socket) polling. Incoming connections, data, etc.

    • Callbacks, aka ticks • One-off timers • Periodic timers
  22. App tells kernel “read from socket 42” Kernel enqueues request

    App continues to run Kernel reads data and throws event App handles event (executes callback)
  23. <?php require __DIR__ . '/vendor/autoload.php'; $loop = React\EventLoop\Factory::create(); $socket =

    new React\Socket\Server($loop); $http = new \React\Http\Server($socket); $counter = 0; $http->on( 'request', function (\React\Http\Request $request, \React\Http\Response $response) use (&$counter, &$loop) { $counter++; echo "Handling request number $counter" . PHP_EOL; $headers = ['Content-Type' => 'text/html']; $response->writeHead(200, $headers); $process = new \React\ChildProcess\Process('fortune'); $process->start($loop); $process->stdout->on( 'data', function ($output) use (&$response) { $response->write($output); } ); $process->on( 'exit', function ($rc, $signal) use (&$response) { $response->end(); } ); } ); echo "Socket server listening on port 4000.\n"; echo "You can connect to it by running: telnet localhost 4000\n"; $socket->listen(4000); $loop->run();
  24. Hack’s Approach • Async functions are marked with an ‘async’

    keyword • Functions return an `Awaitable` • Awaitables are similar to promises - they represent a future value
  25. <?php function fibonacci(int $n): float { $n1 = 0; $n2

    = 1; $n3 = 1; for ($i = 2; $i < $n; ++$i) { $n3 = $n1 + $n2; $n1 = $n2; $n2 = $n3; } return (float)$n3; } function getData(): string { return file_get_contents('data/data.txt'); } $fib = fibonacci(1024); $data = getData(); echo getData() . PHP_EOL . $fib . PHP_EOL;
  26. <?hh async function fibonacci(int $n): Awaitable<float> { $n1 = 0;

    $n2 = 1; $n3 = 1; for ($i = 2; $i < $n; ++$i) { $n3 = $n1 + $n2; $n1 = $n2; $n2 = $n3; } return (float)$n3; } async function getData(): Awaitable<string> { return file_get_contents('data/data.txt'); } async function printData(): Awaitable<void> { $fib = fibonacci(1024); $data = getData(); $results = await \HH\Asio\v([$fib, $data]); echo join(PHP_EOL, $results); } printData();
  27. <?hh async function fibonacci(int $n): Awaitable<float> { $n1 = 0;

    $n2 = 1; $n3 = 1; for ($i = 2; $i < $n; ++$i) { $n3 = $n1 + $n2; $n1 = $n2; $n2 = $n3; if (!($n%100)) { await RescheduleWaitHandle::create(RescheduleWaitHandle::QUEUE_DEFAULT, 0); } } return (float)$n3; } async function getData(): Awaitable<string> { return file_get_contents('data/data.txt'); } async function printData(): Awaitable<void> { $fib = fibonacci(1024); $data = getData(); $results = await \HH\Asio\v([$fib, $data]); echo join(PHP_EOL, $results); } printData();
  28. Why Async? • Tackling the C10K (or C10M!) problem •

    Minimize latency and idleness - these are cost killers! • (Possibly) cleaner separations of concerns
  29. Things to Remember • Async is not always faster than

    sync. Sometimes it’s slower. • It can be hard to conceptualize the benefits of async in the mind • It can be hard to recognize the benefits of async on a laptop
  30. References • https://icicle.io/docs/manual/introduction/ • http://php.net/manual/en/function.stream-select.php • https://blog.domenic.me/youre-missing-the-point-of-promises/ • https://www.sitepoint.com/an-introduction-into-event-loops-in-php/ •

    http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/ • http://blog.kgriffs.com/2012/09/18/demystifying-async-io.html • https://docs.hhvm.com/hack/async/introduction