hack の async / await を利用して非同期処理を行う

PHPを高速で実行できるHHVMですが、プログラムをhackで記述する事で更なる恩恵を得る事ができます。
その中でも特に画期的なのが async / await を使った非同期処理の実現です。
公式のマニュアルから抜粋してその利点をまとめます。

出典
-[Async / introduction](https://docs.hhvm.com/hack/async/introduction)

# Asyncとは
Hackは`async`という協調的マルチタスキングをプログラムから利用できる機能を提供します。これによりコードからasync機構を使うことで入出力(I/O)レイテンシやデータの取得処理を隠蔽できます。コード中でネットワークアクセスやデータベースのクエリを待機する処理がある場合、`async`は入出力に関わらない他の処理を行う事で、待機時間を最小化します。
`async`はマルチスレッドではありません。HHVMは PHP/Hack のコードを一つのメインリクエストスレッドで実行します。しかしMySQLのクエリのようなその他の処理はコードの実行時間を使わずに実行できるのです。

# 制限

- 全ての PHP/HAck コードはメインリクエストスレッドで実行される。
- ブロッキングなAPI (例 mysql_query(), sleep()) は自動的に**非同期な処理に変換されません。** これはそれぞれの実行順序が変わる事によって予期しない影響が起こることが安全ではない為です。

# Asyncの例:cURL

asyncを使わないで2つのcURLリクエストを行うと次のようになります。

```hack
<?hh

namespace Hack\UserDocumentation\Async\Intro\Examples\NonAsyncCurl;

function curl_A(): mixed {
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, "http://example.com/");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  return curl_exec($ch);
}

function curl_B(): mixed {
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, "http://example.net/");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  return curl_exec($ch);
}

function main(): void {
  $start = microtime(true);
  $a = curl_A();
  $b = curl_B();
  $end = microtime(true);
  echo "Total time taken: " . strval($end - $start) . " seconds" . PHP_EOL;
}

main();
```

出力
```
Total time taken: 1.050155878067 seconds
```

-[3v4l.orgでの実行例](https://3v4l.org/vnhn1)

上記の例では`curl_A()`の中の`curl_exec()`は全ての処理をブロックしています。その結果、`curl_B()`が`curl_A()`と独立した処理であっても`curl_A()`の処理が終わるまで待ってから実行されます。

![実行の流れ](https://docs.hhvm.com/s/a6a9ac56565ef538/images/async/curl-synchronous.png)

幸い、HHVMは非同期版の`curl_exec()`を提供しています。

```hack
<?hh

namespace Hack\UserDocumentation\Async\Intro\Examples\Curl;

async function curl_A(): Awaitable<string> {
  $x = await \HH\Asio\curl_exec("http://example.com/");
  return $x;
}

async function curl_B(): Awaitable<string> {
  $y = await \HH\Asio\curl_exec("http://example.net/");
  return $y;
}

async function async_curl(): Awaitable<void> {
  $start = microtime(true);
  list($a, $b) = await \HH\Asio\v(array(curl_A(), curl_B()));
  $end = microtime(true);
  echo "Total time taken: " . strval($end - $start) . " seconds" . PHP_EOL;
}

\HH\Asio\join(async_curl());
```

出力
```
Total time taken: 0.74790596961975 seconds
```

-[3v4l.orgでの実行例](https://3v4l.org/pVGq9)

非同期版の`curl_exec()`はcURLのレスポンスを待っている間にスケジューラーに他のコードの実行を許します。この場合は非同期的に`curl_B()`をさらに実行します。スケジューラーが処理を実行するともう一つの非同期な`curl_exec()`が実行されます。HTTPリクエストは演算処理よりは遅い為、メインスレッドはリクエストが終わるまでアイドルします。

![](https://docs.hhvm.com/s/e252a849fc0870e8/images/async/curl-async.png)

実行順序は大体、上記のようになりますが、そうでない場合もあります。例えば、`curl_B()`のリクエストが`curl_A()`のHTTPより大幅に速い場合は`curl_B()`の処理が`curl_A()`の処理が完了する前に完了します。
非同期処理による速度向上の恩恵はネットワークの状態はDNSのキャッシュなどにより変動的です。ですが劇的な効果になりえます。

# まとめ

HHVMを使ってコードを実行する場合、入出力に関する部分だけでもhackを使って書き換える事で非同期処理を実現する事ができます。リモートのWEBAPIコールやデータベースの処理を順次実行するようなアプリケーションの場合は大幅な高速化を実現できる可能性があります。
非同期処理をPHPで実現する為に複雑なバックエンドを構築する必要がある場合は、HHVMの備える非同期処理の利用も検討してみるとよいのではないでしょうか。

また非同期処理を実現する際には`\HH\Asio`配下にあるコレクションの処理や関数を確認しておくと良いでしょう。
- [\HH\Asio\m](https://docs.hhvm.com/hack/reference/function/HH.Asio.m/)
- [非同期対応の拡張 mysql, memcache, curl, stream](https://docs.hhvm.com/hack/async/extensions)