猫效应 3 互斥意外行为

St.*_*St. 0 scala cats-effect

我偶然发现了一个意想不到的cats.effect.std.Mutex行为(它不适用于某些情况)。看来我错过了一些核心理解,Async但还没有找到根本原因。

\n

进口:

\n
import cats.{Applicative, FlatMap}\nimport cats.effect.std.{Console, Mutex}\nimport cats.effect.*\nimport cats.implicits.*\nimport cats.syntax.all.*\n\nimport scala.concurrent.duration.*\n
Run Code Online (Sandbox Code Playgroud)\n

假设我们有一个服务

\n

工作场景

\n
  class Service[F[_] : Async : FlatMap : Console](mutex: Mutex[F]) {\n    def run(name: String): F[Unit] =\n      for\n        _ \xe2\x86\x90 Console[F].println(s"[$name] entered [run] within [${Thread.currentThread().getName}]")\n        _ \xe2\x86\x90 mutex.lock.surround {\n          for {\n            _ \xe2\x86\x90 Console[F].println(s"[$name] entered [locked] within [${Thread.currentThread().getName}]")\n            _ \xe2\x86\x90 Async[F].sleep(2.seconds)\n            _ \xe2\x86\x90 Console[F].println(s"[$name] almost left [locked] within [${Thread.currentThread().getName}]")\n          } yield ()\n        }\n        _ \xe2\x86\x90 Console[F].println(s"[$name] left [run] within [${Thread.currentThread().getName}]")\n      yield ()\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

和一个跑步者

\n
object MutextTests extends IOApp:\n\n  def run(args: List[String]): IO[ExitCode] =\n    (for\n      mutex \xe2\x86\x90 Mutex[IO]\n      service \xe2\x86\x90 IO.pure(Service(mutex))\n      f1 \xe2\x86\x90 service.run("Alice").start\n      f2 \xe2\x86\x90 service.run("Bob").start\n      _ \xe2\x86\x90 f1.join\n      _ \xe2\x86\x90 f2.join\n    yield ()).as(ExitCode.Success)\n
Run Code Online (Sandbox Code Playgroud)\n

与预期输出

\n
[Alice] entered [run] within [io-compute-2]\n[Bob] entered [run] within [io-compute-2]\n[Alice] entered [locked] within [io-compute-blocker-5]\n[Alice] almost left [locked] within [io-compute-1]\n[Bob] entered [locked] within [io-compute-blocker-3]\n[Alice] left [run] within [io-compute-blocker-1]\n[Bob] almost left [locked] within [io-compute-2]\n[Bob] left [run] within [io-compute-blocker-2]\n
Run Code Online (Sandbox Code Playgroud)\n

但是如果我替换Console[F]Async[F]互斥体就会停止工作:

\n

不工作场景

\n
[Alice] entered [run] within [io-compute-2]\n[Bob] entered [run] within [io-compute-2]\n[Alice] entered [locked] within [io-compute-blocker-5]\n[Alice] almost left [locked] within [io-compute-1]\n[Bob] entered [locked] within [io-compute-blocker-3]\n[Alice] left [run] within [io-compute-blocker-1]\n[Bob] almost left [locked] within [io-compute-2]\n[Bob] left [run] within [io-compute-blocker-2]\n
Run Code Online (Sandbox Code Playgroud)\n

与意外的输出

\n
[Alice] entered [run] within [io-compute-1]\n[Bob] entered [run] within [io-compute-1]\n[Alice] entered [locked] within [io-compute-5]\n[Bob] entered [locked] within [io-compute-4]\n[Alice] almost left [locked] within [io-compute-5]\n[Alice] left [run] within [io-compute-5]\n[Bob] almost left [locked] within [io-compute-6]\n[Bob] left [run] within [io-compute-6]\n
Run Code Online (Sandbox Code Playgroud)\n

Lui*_*rez 6

问题不在于,而在于+Mutex的使用。 您正在构建要传递到 的整个程序之前进行打印。因此,给人的印象是两种纤维同时获得了;但实际上,两者都试图同时获取它,但只有一个人能够访问它。pureprintln
lock.surroundMutex

要恢复预期的行为,您应该使用delay挂起println (实际上正确的事情是blocking,最好的事情是直接使用ConsoleIO.println
您可以在这里看到这种情况:https://scastie.scala-lang.org/BalmungSan/szV1P8X5SeiEiYhzItPmRA/1


顺便说一句,有几点注意事项:

  1. Async+FlatMap是多余的,Async已经是一个Monad. 我实际上不确定这如何不会导致编译错误。
  2. cats.implicits.*已被弃用,您所需要的只是cats.syntax.all.*
  3. 不要手动startjoin纤,很容易出错。总是更喜欢更高级别的组合器,例如parTupled.
  4. 在CE应用程序上打印当前线程名称通常没有多大帮助。

我在这里有几个例子和资源来熟悉整个“程序即值”范式: https: //github.com/BalmungSan/programs-as-values希望它能有所帮助:D (过程中的英文录音)待恢复)