今日のゴール
- Nimの非同期プログラミングの基礎を理解する
- nindaの非同期APIを効果的に使う
- 並行処理のパターンを実装する
なぜ非同期が重要か
タプルスペースは複数のプロセスが協調する仕組みです。効率的な協調には非同期処理が欠かせません。
同期処理では待機時間が無駄になりますが、非同期処理では複数のWorkerが並行して動作します。
Nimのasync/await
基本構文
import std/asyncdispatch
# 非同期関数の定義
proc fetchData(): Future[string] {.async.} =
await sleepAsync(100) # 非同期で待機
return "data"
# イベントループの実行
waitFor main()
Future[T]型
Future[T]は「いずれT型の値が得られる」という約束を表します。
| 状態 | 説明 |
|---|---|
| 未完了 | 処理中、まだ値がない |
| 完了 | 値が準備できた |
| 失敗 | 例外が発生した |
nindaの非同期API
すべての操作に非同期版(*Async)があります。
| 操作 | 同期版 | 非同期版 |
|---|---|---|
| 書き込み | write() |
writeAsync() |
| 読み取り | read() |
readAsync() |
| 取り出し | take() |
takeAsync() |
| 非ブロッキング | tryRead() |
tryReadAsync() |
| 全件取得 | readAll() |
readAllAsync() |
# 基本的な使い方
await ts.writeAsync(toTuple(strVal("task"), intVal(1)))
let result = await ts.readAsync(pattern)
let taken = await ts.takeAsync(pattern)
asyncCheckとawaitの違い
await: 完了を待つ
await task1() # task1が完了するまで待つ
await task2() # task1完了後にtask2を実行
asyncCheck: 投げっぱなし
asyncCheck task1() # task1を開始して即座に次へ
asyncCheck task2() # task2も開始
# task1とtask2は並行実行される
使い分けの図解
並行処理パターン
パターン1: 並行ワーカー
proc worker(id: int) {.async.} =
while true:
let task = await ts.tryTakeAsync(taskPattern)
if task.isSome:
echo "Worker ", id, ": Processing"
await sleepAsync(100)
# 3つのワーカーを並行起動
asyncCheck worker(1)
asyncCheck worker(2)
asyncCheck worker(3)
パターン2: 待ち合わせ(Rendezvous)
2つのプロセスが互いを待ち合わせるパターン。
proc processA() {.async.} =
await ts.writeAsync(toTuple(strVal("sync"), strVal("A_ready")))
discard await ts.takeAsync(toPattern(strVal("sync"), strVal("B_ready")))
echo "A: Both ready!"
まとめ
学んだこと
| トピック | ポイント |
|---|---|
| async/await | Nimの非同期プログラミングの基礎 |
| Future[T] | 将来の値を表す型 |
| 非同期API |
*Async サフィックスの操作 |
| await vs asyncCheck | 逐次 vs 並行 |
いつ使うか
| 場面 | 推奨 |
|---|---|
| 結果が必要 | await |
| バックグラウンド処理 | asyncCheck |
| 複数ワーカー起動 | asyncCheck |
演習問題
問題6-1: 並行カウンター
3つのプロセスが並行してカウンターをインクリメントするプログラムを作成してください。
ヒント
- カウンターは
("counter", 値)というタプルで表現 - インクリメント =
take→ 値+1 →write - 最終的な値は予測可能か?(タプルスペースの操作は原子的であることを思い出そう)
問題6-2: パイプライン
3段階のパイプライン(入力→変換→出力)を非同期で実装してください。
ヒント
- 3つのスペース:
input,transform,output - 各段階は独立した
asyncプロセス - 入力プロセス: 数値を
inputスペースに書き込む - 変換プロセス:
inputから取り出し、2倍にしてtransformへ - 出力プロセス:
transformから取り出して表示
問題6-3: タイムアウト付き待機
最大5秒間タスクを待ち、タイムアウトしたら諦めるワーカーを実装してください。
ヒント
-
readAsync(pattern, timeout = 5000)を使用 -
TimeoutError例外をキャッチ - Day 8で詳しく学びます