关于ZIO库及其原理

282 阅读1分钟

简介

为了在不阻塞当前进程的情况下执行一个效果,我们可以使用ZIO库中的纤维,它是一种轻量级的并发机制。

在ZIO库中,我们可以分叉任何IO[E, A]来立即产生一个UIO[Fiber[E, A]]。提供的Fiber可以用来加入光纤,这将在产生光纤的值时恢复,或者中断光纤,这将立即终止光纤并安全地释放光纤在ZIO库中获得的所有资源。

val example =
  for {
    fiber1   <- exampleData(data).fork  // IO[E, example]
    fiber2   <- validateData(data).fork // IO[E, Boolean]
    // Do other stuff
    valid    <- fiber2.join
    _        <- if (!valid) fiber1.interrupt
                else IO.unit
    example <- fiber1.join
  } yield example

操作

fork和join

当我们需要启动一个光纤时,我们必须分叉一个效应,这就给我们一个光纤。这类似于启动一个Java线程或在Java线程池中添加一个新的线程,它是同一个概念。另外,加入是一种等待该光纤计算其值的方式。我们将等待,直到它完成。

import zio._
import zio.console._
import zio.clock._
import zio.duration._
for {
  fiber <- (sleep(3.seconds) *>
    putStrLn("Hello, after 3 second") *>
    ZIO.succeed(10)).fork
  _ <- putStrLn(s"Hello, to ZIO Library!")
  res <- fiber.join
  _ <- putStrLn(s"Our fiber succeeded with $res")
} yield ()

fork0

这个更强大的fork的变体,叫做fork0,允许指定监督者,这个监督者将从被分叉的光纤中传递不可恢复的错误,包括那些发生在finalizers中的错误。这个监督者需要被指定。如果没有,父光纤监督器将被使用,递归到根处理程序,它可以在Runtime中被指定(默认监督器只是打印堆栈跟踪)。

forkDaemon

使用ZIO#forkDaemon,效果是分叉到一个连接到全局范围的新光纤。这意味着,当之前执行返回效果的光纤终止时,分叉的光纤将继续运行。

在下面的例子中,我们有三个效果:内部、外部和myApp。使用ZIO#forkDaemon,外部效果分叉内部效果。在myApp效果中,内部光纤通过使用ZIO#fork方法被分叉,并在三秒后中断。在全局范围内fork,内层效果不会被中断,并继续做它的工作。

val inner = putStrLn("Inner job is running.")
  .delay(1.seconds)
  .forever
  .onInterrupt(putStrLn("Inner job interrupted.").orDie)

val outer = (
  for {
    f <- inner.forkDaemon
    _ <- putStrLn("Outer job is running.").delay(1.seconds).forever
    _ <- f.join
  } yield ()
).onInterrupt(putStrLn("Outer job interrupted.").orDie)

val myApp = for {
  fiber <- outer.fork
  _     <- fiber.interrupt.delay(3.seconds)
  _     <- ZIO.never
} yield ()

中断

每当我们想移除我们的光纤时,我们可以简单地调用中断。直到所有的纤维终结者都被执行,并且中断操作已经完成或被中断,中断操作将不会恢复。这为构建程序提供了一种不泄漏资源的手段。

等待

你可以对光纤调用await来检查它是否成功或失败。一旦我们调用await,该光纤将被等待终止,我们将得到它的值作为一个Exit。一个成功或失败的值将被返回。

import zio.console._
import zio.random._
for {
  booleanValue <- nextBoolean
  fiber <- (if (booleanValue) ZIO.succeed(10) else ZIO.fail("The boolean was false")).fork
  exitValue <- fiber.await
  _ <- exitValue match {
    case Exit.Success(value) => putStrLn(s"Fiber is succeeded with $value")
    case Exit.Failure(cause) => putStrLn(s"Fiber is failed")
  }
} yield ()

在ZIO库中,await ,与join相似,但比join低级。每当我们调用join时,如果底层的光纤失败了,我们在试图连接它时也会遇到同样的错误。

平行性

zipPar方法可以用来并行地执行动作。

def largeCompute(m1: Matrix, m2: Matrix, v: Matrix): UIO[Matrix] =
  for {
    t <- computeInverse(m1).zipPar(computeInverse(m2))
    (i1, i2) = t
    r <- applyMatrices(i1, i2, v)
  } yield r

竞速

在ZIO光纤中,两个IO动作可以进行竞赛,这意味着它们将并行执行,第一个成功完成的动作的值将被返回。

result(100) race result(200)

在zio fiber中,竞赛组合器是资源安全的,这意味着如果两个动作中的一个返回一个值,另一个将中断,以防止浪费资源。

总结

通过使用ZIO,我们只成功地发掘了它的一小部分潜力--通过在效果中使用ZIO Fibers。开箱即用的并行性、不可更改性、参考透明性和包裹式的副作用管理,使得这个例子的编写不费吹灰之力,而且非常愉快。