🐣

Nimで非同期を試してみた

2023/03/19に公開

今回は、Nim言語での非同期処理についての、記事を書いていきます。

非同期とは、 2つ以上の事象が同時に発生したり、関連する複数の事象が互いの完了を待たずに発生したりする概念を指します(by ウィキペディア)

Nimでの非同期処理のプログラム

1. 非同期化の実行結果

3つの非同期関数を使って、簡単なプログラムを作成してみました。

sample01.nim
import asyncdispatch
import std/[times, strutils]
 
let start = cpuTime()

# 0.1秒毎に経過時間を表示
proc timeLine() {.async.} =
  for i in 1..10:
    await sleepAsync(100)
    echo "経過時間 ",
         i*100, "ms ",
         split($((cpuTime() - start)*1000), '.')[0], "ms (実時間)"

# waitミリ秒に1度だけ表示し、結果を返す
proc delayMessege(message: string, wait: int): Future[string] {.async.} =
  await sleepAsync(wait)
  echo message
  result = "===終了==="

# 開始位置
let
  msgFuture1 = delayMessege("花子> はい!", 230)
  msgFuture2 = delayMessege("太郎> は...はぃ", 880)
  timeLineFuture = timeLine()

echo "教師> この問題に答えれる人は、いますか?" 

# 全てが終わるまで待機
waitFor timeLineFuture and msgFuture1 and msgFuture2

echo "教師> じゃあ、太郎君"
if msgFuture2.finished :
  echo "太郎> 答えが、わかりません! return=" & msgFuture2.read()
else :
  echo "太郎> え!?、なんで俺?"

プログラムの説明

  • asyncdispatchを宣言し、非同期化する関数に対してプラグマ{.async.}を指定すれば、非同期化が行えます。
  • 非同期化する関数内でawait sleepAsyncで、指定時間まで実行を待機させます(sleepAsyncの引数はミリ秒単位)
  • delayMessege関数は、文字列を返すようにするため、関数の宣言にFuture[string]を指定します。timeLine関数はvoidなので付けなくても良いですが、戻り値はFuture[void]になります。
  • 各非同期化を行う関数は、Futureオブジェクトが返りますので、それぞれ引数を指定します。(msgFuture1msgFuture2timeLineFutureFuture[void]でも戻り値は返ると言う事です)
  • 全ての非同期化を行う関数が終了するには、waitForで待ちます。
  • Futureオブジェクトの戻り値finishedが、trueの場合に正常に非同期処理の関数が処理を終了した事が、確認出来ます。
  • また、非同期化した関数からの戻り値を得るには、msgFuture2.read()で戻り値を抽出する事が出来ます。

下記のようにターミナル上から実行

nim r --hints:off .\sample01.nim

実行結果

教師> この問題に答えれる人は、いますか?
経過時間 100ms 110ms (実時間)
経過時間 200ms 221ms (実時間)
花子> はい!
経過時間 300ms 329ms (実時間)
経過時間 400ms 440ms (実時間)
経過時間 500ms 561ms (実時間)
経過時間 600ms 670ms (実時間)
経過時間 700ms 779ms (実時間)
太郎>...はぃ
経過時間 800ms 889ms (実時間)
経過時間 900ms 1011ms (実時間)
経過時間 1000ms 1122ms (実時間)
教師> じゃあ、太郎君
太郎> 答えが、わかりません! return====終了===

ここで問題
経過時間を見ると、100ms毎に経過表示させていますが、実際の表示結果と大体10ms程の誤差が生じてきて、生徒の太郎君が発言した時間と一致しません。
※実測時間では一致します。

2. 非同期化処理をwaitForで止めなかった場合

先ほどのプログラムから、waitForで待ちを行わなかった場合にどうなるかを確認していきます。

先ほどのプログラムからwaitForをコメントにする
# 全てが終わるまで待機 (waitForをコメントにする)
# waitFor timeLineFuture and msgFuture1 and msgFuture2

実行結果

教師> この問題に答えれる人は、いますか?
教師> じゃあ、太郎君
太郎> え!?、なんで俺?

プログラムの説明

  • waitForをコメントすると、非同期化した関数が実行する前に処理が終わるため、msgFuture2.finishedはfalseが返ってきます。
  • そのため、プログラム処理の「太郎> え!?、なんで俺?」と出力して処理が終わります。
  • また、非同期化した関数が実行途中の場合に、戻り値を確認すると(msgFuture2.read()を指定する)、Exceptionエラーになりプログラムが終了します。

Futureオブジェクトの中身

lib/pure/asyncfutures.nimに記載されているFutureオブジェクトの中身ですが、ざっくりと以下のようになります。
戻り値のvalueは、Future関数を作って、newFutureを呼び出せば簡単に戻り値は取り出せますが、そうでない場合は、read()で値を取り出します。

Futureオブジェクト
type
  Future[T]  ref object
    value: T
    callbacks: CallbackList
    finished: bool
    error: ref Exception

おわりに

今回は、時間も無く小ネタで非同期化を説明しました。
また、threadingについても、調べたのですが、サンプルを書いても何故かエラー表示されるのですが、実際には動作はしますけど…、Nimではスレッド処理がまだ安定していないって事でしょうかね?

Discussion