Nimの非同期IOとマルチスレッド


nimは非常に書きやすくお気に入りの言語です。
ついに1.0になりました🎉
そんなNimの非同期に触れてみたいと思います。

※nim version 1.0.2で動かしています。
## 非同期IO

asyncdispatchモジュールを使います。

``` cs
import asyncdispatch, httpclient

proc asyncRequest(url: string): Future[AsyncResponse] {.async.} =
    let client = newAsyncHttpClient()
    let response = await client.get(url)
    return response

proc main() {.async.} =
    try:
        let response = await asyncRequest("https://forum.nim-lang.org/")

    except:
        echo "Operation Failed"

waitFor main()
```

非同期IOはasync pragmaと awaitをつけるだけで簡単に行うことができます。

`This allows you to write asynchronous code in a synchronous style and works similar to C#'s await`

とあるとおりC#ライクです。
戻り値はFuture型になっていて、こちらもC#のTaskにそっくりです。
Python風で非同期はC#風って嬉しい。

### Future作り方

newFutureで作れます。
成功でcomplete 失敗でfailを返せばいいようです。

``` nim
import os, asyncdispatch


proc createFuture1(): Future[string] =
    var retFuture = newFuture[string]("createFuture1")
    echo "1: Start"
    sleep(2000)
    echo "1: End"
    retFuture.complete("Future1完了")
    return retFuture

proc createFuture2(): Future[string] =
    var retFuture = newFuture[string]("createFuture2")
    echo "2: Start"
    sleep(5000)
    echo "2: End"
    retFuture.complete("Future2完了")
    return retFuture

proc main() {.async.} =
    try:
        var fut1 = await createFuture1()
        var fut2 = await createFuture2()

        echo fut1
        echo fut2

    except:
        echo "Operation Failed"

waitFor main()
```

```
1: Start
1: EndWait
2: Start
2: End
Future1完了
Future2完了
```

###  複数のFuture完了待ち

まとめて処理したい場合は await allですべて完了するまで待つことができるようです。すばらしい

``` nim
proc main() {.async.} =
    try:
        var fut1 = createFuture1()
        var fut2 = createFuture2()

        var results = await all(@[fut1, fut2])

        for result in results:
            echo result

    except:
        echo "Operation Failed"

waitFor main()
```

動かしてみます。

```
1: Start
1: End
2: Start
2: End
Future1完了
Future2完了
```

となり、sleepで待って同期的にうごいてしまいます。C#のTask.Run 感覚ではだめでした。
sleepはsleepAsyncを使い、下のように変えるとうまく動きました。

``` nim
import asyncdispatch

proc createFuture1(): Future[string] {.async.} =
    echo "1: Start"
    await sleepAsync(2000)
    echo "1: End"
    return "Future1完了"

proc createFuture2(): Future[string] {.async.} =
    echo "2: Start"
    await sleepAsync(5000)
    echo "2: End"
    return "Future2完了"

proc main() {.async.} =
    try:
        var fut1 = createFuture1()
        var fut2 = createFuture2()

        var results = await all(@[fut1, fut2])

        for result in results:
            echo result

    except:
        echo "Operation Failed"

waitFor main()
```

```
1: Start
2: Start
1: End
2: End
Future1完了
Future2完了
```

イベントループですね。
シングルスレッドで動作するのでsleepで普通に止まります。


## マルチスレッド

普通にスレッド作ることもできますが、threadpoolモジュール を使うのがよさそうです。
スレッドを管理してもらえます。
Spawnすると新しいスレッドで動作し、結果はFlowVar型に包まれます。
Sync()でspawnした処理がすべて完了するまで待ちます。
結果は^で取り出せます。

```nim
import os, threadpool

proc heavyMethod1(): int =
    echo "1: Start"
    sleep(5000)
    echo "1: End"
    return 5

proc heavyMethod2(): int =
    echo "2: Start"
    sleep(2000)
    echo "2: End"
    return 20

var result1 = spawn heavyMethod1()
var result2 = spawn heavyMethod2()
sync()

echo ^result1
echo ^result2
```


```
1: Start
2: Start
2: End
1: End
5
20
```

^だけで完了するまで待ってくれます。

``` nim
var result1 = spawn heavyMethod1()
echo ^result1
var result2 = spawn heavyMethod2()
echo ^result2
```

```
1: Start
1: End
5
2: Start
2: End
20
```

### Parallelステートメント

experimentalですが、
Parallelステートメントを使うとFlowVarとか意識しなくてもよくなります。

``` nim
{.experimental: "parallel".}
parallel:
    var result1 = spawn heavyMethod1()
    var result2 = spawn heavyMethod2()

echo result1
echo result2
```

すっきりでとてもよいですね。
しかしexperimentalだからか私の書き方が悪かったのかstring型だとうまく戻ってくれませんでした。
今後に期待です。


## まとめ

IOバウンドな場合はasync+await
CPUバウンドな場合はspawn 

C#のasync/awaitは一緒になっていてハマりどころだったりします。
わかれているのはわかりやすい。