如何使管道与Haskell的管道库并发?

时间:2017-01-01 01:43:50

标签: haskell concurrency haskell-pipes

我有一些使用Pipes的Haskell代码:

module Main(main) where
import Pipes

a :: Producer Int IO ()
a = each [1..10]

b :: Pipe Int Int IO ()
b = do
  x <- await
  yield (x*2)
  b

c :: Consumer Int IO ()
c = do
  x <- await
  lift $ print x
  c

main :: IO ()
main = runEffect $ a >-> b >-> c

Pipes.Concurrent tutorial演示了使用多个工作人员以及偷工作。我怎样才能在b内做类似的事情?我希望b使用一定数量的工作人员同时执行它的工作。

显然,并发性在这种情况下没有用,但它是我能想到的最简单的例子。在我的实际用例中,我想使用有限数量的工作者同时发出一些Web请求。

1 个答案:

答案 0 :(得分:2)

编辑:我误解了你的要求; 你可以在管道内做到这一点,但我不确定动机是什么。我建议建立可重复使用的管道链,只使用工人调度它们,而不是试图在管道内部建立工人。如果您将其构建到管道中,那么您将失去任何排序保证,即第一个输入是第一个输出。

关于Work Stealing的部分是您正在寻找的部分,此代码基本上是从教程中逐字逐句,但让我们分解其工作原理。这是我们可以做你想做的一种方式:

module Main(main) where
import Pipes
import Pipes.Concurrent

import Control.Concurrent.Async (async, wait)
import Control.Concurrent (threadDelay)
import Control.Monad (forM)

a :: Producer Int IO ()
a = each [1..10]

b :: Pipe Int Int IO ()
b = do
  x <- await
  yield (x*2)
  b

c :: Consumer Int IO ()
c = do
  x <- await
  lift $ print x
  c

main :: IO ()
main = do
  (output, input) <- spawn unbounded
  feeder <- async $ do runEffect $ a >-> toOutput output
                       performGC

  workers <- forM [1..3] $ \i ->
    async $ do runEffect $ fromInput input  >-> b >-> c
               performGC

  mapM_ wait (feeder:workers)

第一行spawn unbounded来自Pipes.Concurrent,它初始化一个&#39;邮箱&#39;具有输入和输出的句柄。起初它让我困惑,但在这种情况下,我们将消息发送到输出并从输入中拉出它们。这类似于golang等语言中的推拉消息通道。

我们指定一个Buffer来说明我们可以存储多少条消息,在这种情况下,我们设置无限制的无限制。

好的,所以邮箱已初始化,我们现在可以创建向其发送消息的Effect。邮箱通道使用STM实现,以便它可以异步收集邮件。

让我们创建一个异步作业,为邮箱提供信息;

feeder <- async $ do runEffect $ a >-> toOutput output
                     performGC

a >-> toOutput output只是普通的管道组合,我们需要toOutput将输出转换为管道。注意performGC调用它也是IO的一部分,它允许Pipes.Concurrent知道在作业完成后进行清理。如果我们愿意,我们可以使用forkIO来运行它,但在这种情况下,我们使用async,以便我们可以等待结果稍后完成。好的,所以我们的邮箱应该异步接收消息,让我们把它们拉出去做一些工作。

workers <- forM [1..3] $ \i ->
  async $ do runEffect $ fromInput input  >-> b >-> c
             performGC

和以前一样,但这次我们只是产生了一些。我们使用fromInput读取输入就像普通管道一样,然后在我们链的其余部分运行它,在我们完成时清理。 input将确保每次拉出一个值时只有一个工作人员收到它。当所有进入output的作业完成(它跟踪所有打开的作业)后,它将关闭input管道并且工人将完成。

如果您在网络工作者场景中使用此功能,您将拥有一个主循环,该循环不断向toOutput output频道发送请求,然后产生任意数量的工作人员,他们会将其引入其管道来自fromInput input