我有一些使用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请求。
答案 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
。