非同期概略
Kazuki Moriyama (森山 和樹)
プロセスとスレッド
プロセス
- 動いているプログラムの単位だと考えればいい
- OSからCPU、メモリなどのリソースを割り当てられる
- カーネルへのシステムコールによって生成される
- 借り受けたメモリはプロセスごとにOSによって分割されているため他のプロセスとデータ共有することが基本できない(できるけど面倒)
スレッド
- プロセスと同じようにプログラムの単位
- プロセスから複数生成される
- 他のスレッドとメモリを共有できる
- メモリの共有は効率化にもつながるが、それ故に排他制御性の難しさも生じる
マルチプロセスとマルチスレッド
- 一つの大きな処理を複数のプログラム単位に分けることで処理時間を短縮することができる
- これらを利用して非同期処理が実現できる
- 方法は大きく分けてマルチプロセス・マルチスレッド
マルチプロセス
- プロセスを複数立ち上げて行うアプリケーション
- メリット
- 一つのプロセス障害への耐性が強い
- プログラミングミスが起こりにくい
- デメリット
- コストが大きい
マルチスレッド
- スレッドを複数立ち上げて行うアプリケーション
- メリット
- メモリ効率などのパフォーマンスがマルチプロセスよりもいい
- 他のスレッドとのデータ共有が容易
- デメリット
- スレッドが死ぬとプログラム全体に致命的な影響を与える場合がある
- 共有データの管理が死ぬほど面倒
非同期処理
- 同期的ではなく、非同期にコードを実行する方式
- これを利用することでアプリケーションの速度を上げることができる
同期的なプログラム
- 普通のコードだと思えばいい
- 上から順に実行されていく
- しかし独立した処理は前の処理の完了を待たずに同時に実行したほうが早くない?という発送が生まれる
- これが非同期処理
# ①
for i in range(100000):
print("hello")
# ②
for i in range(100000):
print("john")
# 通常は①->②の順で実行されるがもし①と②を同時実行できればほぼ倍速で終わるはず
非同期的なプログラム
- 同期部分から処理を切り出して同時に実行するスタイル
- 同時実行するために新たにスレッドやプロセスを複数起動する(マルチスレッド・マルチプロセス)
非同期処理の難しさ
- 速度的なメリットがあるとはいえ考えいといけないことが増える
- スレッド・プロセスを生成する処理
- それらのスケジューリング
- 終了を監視し同期的コードに合流させる処理
- 終了時のスレッド・プロセスの後始末
- 特にマルチスレッドの場合は本当に難しい
- またマルチスレッド・プロセスで速度もあげてもスケーラビリティにいつか限界がくる(C10K problem)
ノンブロッキングIO
- IOに対してブロックしない
- 基本は普通の非同期処理と変わらない
- しかしコールバックと呼ばれるものを処理にたいして登録できる
- コールバックは対象の非同期処理が終了したときに実行される処理
- ノンブロッキングIOを使用することで必要となるスレッド・プロセスが節約できるためC10K problemが解決できる
const fs = require('fs');
const data = fs.readFileSync('/file.md'); // ファイルが読み込まれるまでここでブロック
console.log(data);
moreWork(); // console.log の後に実行されます
const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
if (err) throw err;
console.log(data);
});
moreWork(); // console.log の前に実行されます
非同期処理の難しさへの対応
- 各言語・ライブラリが各々パラダイムを持って複雑さを
ラッピングしている
Future/Promiseスタイル
- 非同期処理の一連の流れをラッピングしたインターフェースを提供する
- 重要なのはインターフェースであることで内部の詳細実装は言語によって異なる
- Scala、js、pythonなど
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
Future(1).map(i => println(i))
Reactive系
- RxやAkkaでの実装がある
- なにかのイベントやデータソース源をオブジェクト(Source/Observable)として用意する
- Source/Observableに対してイベント発火・データ到着時の処理をコールバックで宣言的に記述する
- 実行時にはそのイベント発火などに合わせてコールバックが反応的(reactive)に実行される
Reactive Extension (Rx)
- 基本はデザインパターンのobserver
- そこに非同期の管理と処理をしやすいインターフェースをくっつけてある
- 各言語の実装が用意されている
- モバイルでよく使われる
from time import sleep
count = 0
while True:
sleep(1)
count += 1
print(count)
from rx.subjects import Subject
Subject.interval(1000).subscribe(print)
Actorモデル
- なにかの処理の実行単位をActorというまとまりで強く分割する
- この分割は同じマシン内・外に関わらず行うことができる
- Actor同士はメッセージを介して完全非同期に実行される
- 障害耐性・復帰戦略のためのシステムが組み込まれてる(super visor)
![https___qiita-image-store.s3.amazonaws.com_0_214671_306deabe-e39d-3e7f-812f-4c77307fc42d.png](https://image.docbase.io/uploads/e4d7da56-7fdc-40e7-add0-de05ee771d22.png =WxH)