【问题标题】:FS2 parallel evaluation of items with different discriminatorFS2 对具有不同鉴别器的项目进行并行评估
【发布时间】:2021-12-22 13:35:58
【问题描述】:

在以下示例中,并行评估(打印)具有不同鉴别器("a""b""c")的项目:

package org.example

import cats.effect.std.Random
import cats.effect.{ExitCode, IO, IOApp, Temporal}
import cats.syntax.all._
import cats.{Applicative, Monad}
import fs2._

import scala.concurrent.duration._

object GitterQuestion extends IOApp {

  override def run(args: List[String]): IO[ExitCode] =
    Random.scalaUtilRandom[IO].flatMap { implicit random =>
      val flat = Stream(
        ("a", 1),
        ("a", 2),
        ("a", 3),

        ("b", 1),
        ("b", 2),
        ("b", 3),

        ("c", 1),
        ("c", 2),
        ("c", 3)
      ).covary[IO]

      val a = flat.filter(_._1 === "a").through(rndDelay)
      val b = flat.filter(_._1 === "b").through(rndDelay)
      val c = flat.filter(_._1 === "c").through(rndDelay)

      val nested = Stream(a, b, c)

      nested.parJoin(100).printlns.compile.drain.as(ExitCode.Success)
    }

  def rndDelay[F[_]: Monad: Random: Temporal, A]: Pipe[F, A, A] =
    in =>
      in.evalMap { v =>
        (Random[F].nextDouble.map(_.seconds) >>= Temporal[F].sleep) >> Applicative[F].pure(v)
      }
}

运行这个程序的结果会是这样的:

(c,1)
(a,1)
(c,2)
(a,2)
(c,3)
(b,1)
(a,3)
(b,2)
(b,3)

请注意,具有相同鉴别器的项目之间没有重新排序 - 它们是按顺序处理的。 (a, 2) 永远不会在 (a, 1) 之前打印。

在我的真实场景中,鉴别器的值是不知道的,可能有很多,但我希望有相同的行为,我该怎么做?

【问题讨论】:

    标签: scala scala-cats cats-effect fs2


    【解决方案1】:

    我认为您需要为此推出自己的 groupBy 函数。我认为您必须为每个鉴别器创建一个Queue。然后对于每个Queue 发出一个内部Stream,它从Queue 中提取元素。

    这是我的想法的未经测试且可能很幼稚的实现:

    import cats.effect.std.Queue
    
    val nested = 
      (flat.map(Some(_)) ++ Stream(None))
        .evalScan(Map.empty[String, Queue[IO, Option[(String, Int)]]] -> Option.empty[Stream[IO, (String, Int)]]){
          case ((map, _), t @ Some((key, value))) =>
            if (map.contains(key))
              map(key).offer(t).as(map -> None)
            else {
              for {
                q <- Queue.unbounded[IO, Option[(String, Int)]]
                _ <- q.offer(t)
                r = (map + (key -> q)) -> Some(Stream.fromQueueNoneTerminated(q))
              } yield r
            }
          case ((map, _), None) => 
          // None means the flat stream is finished
            map.values.toList.traverse(_.offer(None))
              .as(Map.empty -> None)
        }
        .map(_._2).unNone
    
    val parallelism: Int = ???
    
    nested
      .map(_.through(rndDelay))
      // produce and consume in parallel in order to 
      // avoid deadlocks in case of bounded parJoin
      .prefetchN(parallelism)
      .parJoin(parallelism)
      .printlns
      .compile
      .drain
      .as(ExitCode.Success)
    

    【讨论】:

      【解决方案2】:

      我相信 broadcastThrough 可以满足您的需求。
      (但请务必仔细检查 Scaladoc

      为了简单起见,我直接使用IO,但它应该很容易适应抽象F[_]

      def discriminateProcessing[A, B](stream: Stream[IO, A])(discriminators: List[A => Boolean])(pipe: Pipe[IO, A, B]): Stream[IO, B] = {
        val allPipes: List[Pipe[IO, A, B]] = discriminators.map { p =>
          s => s.filter(p).through(pipe)
        }
      
        stream.broadcastThrough(allPipes : _*)
      }
      

      会这样使用:

      val result = discriminateProcessing(stream = flat)(discriminators = List(
        _._1 === "a",
        _._1 === "b",
        _._1 === "c"
      )) { s =>
        s.evalMap { v =>
          random.nextDouble.map(_.seconds).flatMap(IO.sleep).as(v)
        }
      }
      

      你可以看到代码在运行here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-18
        • 1970-01-01
        • 2022-01-16
        • 1970-01-01
        • 2015-04-06
        • 2020-08-23
        相关资源
        最近更新 更多