非同期ってナンダ

# 前書き

本記事は非同期についてひたすら大雑把に綴っています。主にPocketMine-MPを利用している開発者の方々へ、またphp前提のお話となっております。

# 1. 非同期？

### 疑問

しばしば目にする単語ではありますがイマイチ概要のつかめない、得体の知れないものだと思います。他人のプラグインをのぞいてみると、`AsyncTask`なんかを使っていたり。

非同期ってなんなんだろう...

### 答え

非同期とはズバリ、**メインスレッド**に対して**処理や通信の内容が別の場所で実行される**ものです。

下記のコードを参考にしてみましょう。`echo "1"`が実行された後`async()`という非同期関数が実行されると、この場合`echo "2"`が`async()`の実行結果を**待たずに**実行されます。

```php
echo "1";
async();
echo "2";
```

### つまり？

要はメインスレッドで重い処理をしてしまうと、**その処理が終わるまで次の処理に移行できない**わけです。別の場所に役割を分担できればその分早く処理が終わりますね。

例えばWEditなんかを使っていると、Blockを大量に設置してしまった場合にBlockを全て置き終わるまで待たされてしまいます。

**Blockを設置するという部分を別の場所にやらせておけば、設置している時間に待たされなくて済みますね。**

# 2. スレッド？  

phpでの非同期処理には<a href="https://github.com/krakjoe/pthreads">pthreads</a>が今まで幅広く使われてきました。メインスレッドに対して処理を待たずに実行される為、非同期として実行されます。したがってスレッドベースのこれらは**マルチスレッド**と呼ばれています。

・Threaded
> スレッドによって実行されるものを表す。**pthreadsが実行できる機能のもと**。

・Thread
> runメソッドを実装することでスレッドが実装できる。要は非同期処理のコア。**処理が終わればスレッドは終了する**。ネイティブスレッドを毎回生成して破棄するからコストが高い。

・Worker
> インスタンスが破棄されるか、Worker::shutdown() が呼ばれるまで**永続的にスレッドを使いまわす**もの！

・Pool
> Workerのグループを作るもの！**与えたThreadedを分散させ(手の空いてるWorkerに割り当て)て並列処理してくれる**。

### もっと詳しく  

`AsyncTask`はThreadedのラッパークラスで、スレッドによって実行されていたタスクの終了を知らせる機能がついています。
・<a href="https://github.com/pmmp/PocketMine-MP/blob/stable/src/scheduler/AsyncTask.php#L154">AsyncTask::onComplietion()</a>

重い処理は別のスレッドに任せるとドンドン楽になりますね！

### 罠
などとは言いきれません。Threadには重大な制約があります。それが…

**Threadにはリソース型が渡せない。**
OSによりけりですが、とにかく実現が難しいです。

string型やint型と言った**プリミティブ型は**別のスレッドから別のスレッドに渡す事が出来ますが、**PlayerやBlockといったオブジェクト**は渡すことが出来ません。
勿論`Server::getInstance()`もマルチスレッドでは実行できません！ナンテコッタイ！

![787ee046.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/292704/3298424e-104c-8f13-27d8-bf17f63d1f0f.jpeg)

### 救済その1

リソース型が持ち越せない、ということはDBを扱うときに`SQLite3`や`mysqli`、ORMのオブジェクトも渡せないという事です。なんとかならないのだろうか？

頭を悩ませてついに思いついたのは、DBへのアクセスを永遠にスレッドに閉じ込めるものでした。その為、再帰処理を実装しなければなりません。

しかし、例えば`while(true)`で閉じ込めますと中身は高速で同じことを繰り返しますので、対策として`wait/notify`を使う、`sleep()`を使うといった例が挙げられます。

> <a href="https://gist.github.com/ribnil/4785038772e048b402ca4a74a1a3ee79">wait/notify サンプル</a>

### 救済その2

**メモリを犠牲にしてトランザクションにしてみる。**
最初に思いついたのがこれで、要はDBへのアクセスを最小限にしてデータをメモリにずっと持たせておくという事。サーバーがシャットダウンしたときにまとめて保存したり。

### ライブラリ

モジュールの`mysqli`、 `SQLite3`には非同期でクエリを投げてくれるオプションがないわけではありません。が、使うなら他に使いやすいライブラリやフレームワークを探したほうが良いでしょう。

・<a href="https://github.com/poggit/libasynql">libasynql</a>
・<a href="https://github.com/SOF3/await-generator">AwaitGenerator</a>

> 非同期でDBにアクセスできるし、SQLiteやMySQLとかにも対応してる。AwaitGeneratorも使えるといいね。

# 3. 使い方を教えてくれよ

### 実践

それでは`AsyncTask`を使って実装してみましょう。

```php
class K extends AsyncTask {
    __construct(
        private string $text
    ) {}

    public function onRun() {
        echo "onRun: ".$this->text.PHP_EOL;
    }

    public function onCompletion() {
        echo "onCompletion: ".$this->text.PHP_EOL;
    }
}
```

### 解説

この場合の実行結果はどうなるでしょうか？
まず、コンストラクタにtextという文字列が渡されています。次に`onRun`が実行され、コンソールに文字列が出力されます。最後に、スレッドが処理を終えた関数`onCompletion`が実行され、これもまたコンソールに文字列が出力されます。

### もっと  

色んな部分にマルチスレッドを応用できます。例えば<a href="https://github.com/discord-php/DiscordPHP">DiscordPHP</a>は一つのスレッドを占領しなければなりません。

他にも、`curl`などといった外部との通信で時間がかかる処理も非同期で実行するのが無難でしょう。

現時点(2021 12/15)ではPHP8.1がリリースされております。その中で注目されているのが `Fibers`です。これはどこからでも処理を再開できるグリーンスレッドを提供しています。

# 後書き

ここまでみてくれてありがとう！他人の実装を参考にしたり意見を聞いたりすると楽に実装できたりするので、より良い実装を取り入れてみましょう。ちなみにpthreadsよりparallelのほうが推奨されてるっぽい(そりゃそうだ)。

### 谢谢（ホンマにありがとう）

アドベントカレンダー初参加でした。超おもしろかったです。講義時間に文章を考えてました。開催してくれた @WhiteGrouse さん、参加してくれた皆、読んでくれたオマエ！ありがとう！

### 令和コソコソ噂話

二回見直して修正したけど結局内容の勘違いがあって指摘してもらった、恥ずかしい。
そして今日は僕の誕生日！おめでとう！おんぎゃあ！