ZIOの並列処理

# 並列処理の種類
ZIOには並列処理の仕組みが組み込まれており、手軽に通常の処理を並列処理にすることが出来ます。
ZIOの並列処理は一般的なものは以下の2種類に大別されます。

1. ``ZIO.fork``によるもの
1. 関数名の後ろに``Par``が付くもの

これらについて順に見ていきます。

# ``ZIO.fork``による並列処理

以下のようにコードを記載します。

```scala:MainApp.scala
object MainApp extends ZIOAppDefault {
  def run: ZIO[Any, Throwable, Unit] = ApplicationService.consoleOutput().provide(ZLayer.fromZIO(ZIO.attempt {
    import java.text.SimpleDateFormat
    // 任意の日付文字列
    val inpDateStr = "2023/07/25 17:46:00"

    // 取り扱う日付の形にフォーマット設定
    val sdformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")

    // Date型に変換( DateFromatクラスのparse() )
    sdformat.parse(inpDateStr)
  }), ApplicationServiceImpl.layer)
}
```
```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] = for {
    fiber <- (for {
      _ <- ZIO.attempt(Thread.sleep(3000))
      _ <- Console.printLine("並列処理実行")
      result <- ZIO.succeed("並列処理:成功")
    } yield result).fork
    _ <- Console.printLine(
      s"${new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(currentDate)} Hello, World!"
    )
    result <- fiber.join
    _ <- Console.printLine(result)
  } yield ()
}
```
このプログラムを実行すると以下の出力がコンソールから得られると思います。
```shell
2023/07/25 17:46:00 Hello, World!
並列処理実行
並列処理:成功
```

この結果から日時を出力する処理が先に実行され、後から先に記載されている部分が実行され、並列処理が実行されていることが確認できたと思います。
このプログラムについて詳しく見ていきましょう。

## ``ZIO.fork``

まず最初に以下の部分のように``fork``関数を呼び出します。

```scala
    fiber <- (for {
      _ <- ZIO.attempt(Thread.sleep(3000))
      _ <- Console.printLine("並列処理実行")
      result <- ZIO.succeed("並列処理:成功")
    } yield result).fork
```

これにより``for yield``句内の処理が並列して実行されるように指定されます。
返り値を受け取った``fiber``変数はString型ではないため、このままでは後続の処理に使えません。
後続の処理に使うためには続いて``join``関数を呼び出します。

## ``join``関数

並列処理されている処理の返り値を受け取るためには最終的にjoin関数を呼び出す必要があります。

```scala
result <- fiber.join
```

この``result``変数の型はString型であり、他の処理にも利用できる型です。
「並列処理:成功」という文字列であり、コンソールへの出力に使っています。

以上のようにZIO型に対して``fork``関数と``join``関数をセットで使うことにより簡単に並列処理を実行することができます。

# 後ろに``Par``が付く関数による並列処理

関数名の後ろに``Par``が付くものを試す前に``Par``が付かない場合の挙動を確認します。
以下の様にコードを修正します。

```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] = for {
    // 以下に関して警告が表示されるかもしれませんが、動作確認が主目的のため一旦無視します。
    _ <- ZIO.foreach(List.range(1, 100))(n => Console.printLine(n))
  } yield ()
}
```
``ZIO.foreach``関数はListと関数を引数に取り、リストの各要素に対して引数の関数を適用し、最終的にZIO型のListを返す関数になります。
修正後実行すると以下の様な出力が得られると思います。

```shell
1
2
3
4
5
(省略)
95
96
97
98
99
```

1から99までの数が何度実行し返したとしても順に出力されるはずです。
これは処理が並列ではなく直列に実行されていることを意味しています。

次に以下の様に``ZIO.foreach``関数を``ZIO.foreachPar``関数に修正します。
```scala:ApplicationServiceImpl.scala
    _ <- ZIO.foreachPar(List.range(1, 100))(n => Console.printLine(n))
```

これを実行すると以下の様な出力が得られます。

```shell
36
86
3
12
14
79
42
(省略)
24
25
26
21
23
22
```
先ほどとは打って変わり、順序関係なく数字が出力されます。
また、繰り返す毎に異なる出力結果になるはずです。
このことから``ZIO.foreachPar``関数により並列実行が行われたことがわかります。
``ZIO.foreachPar``関数の他にも同様の``ZIO.partitionPar``関数や``ZIO.mergeAllPar``関数があります。

# ``Fiber``について(並列処理の仕組み)

最後にこれらの並列処理の仕組みについて簡単に触れたいと思います。
このようなZIOの並列処理は一般的なJavaプログラムの並列処理で使われるThreadではなくFiberによって行われます。

FiberについてはThreadを改良したものとなっており、1つのThreadに対して複数のFiberが対応する関係になっています。
以下の様な長所があります。

1. Threadと比べて生成可能な数が非常に多い
1. 生成コストがThreadに比べて低い
1. Fiberは処理に失敗した場合と成功した場合の返り値について型付けされている

以下のページにFiberの概要が載っています。

https://zio.dev/reference/fiber/

# 終わりに

今回はZIOの並列処理について見てきました。
今回でバッチ編は終わりです。
次回からバックエンド編に入ります。
最初は``zio-http``によるGETメソッド、POSTメソッドの処理等について見ていきます。

前章:[ZIOライブラリ利用(DB接続)](https://qiita.com/wc_sangen/private/37436c567f4015a02ba7)
次章:[zio-http](https://qiita.com/wc_sangen/private/63d3d3957245b90d2012)

# 演習

1. 今回の2つの並列処理について実際に動かして挙動を確かめてください。
    1. ``ZIO.fork``関数を使ったものの解答例は以下になります。
        https://github.com/hatuda/zio-practice/tree/CHAPTER5-Fork
    1. 末尾が``Par``となっている関数を使ったものの解答例は以下になります。
        https://github.com/hatuda/zio-practice/tree/CHAPTER5-Par