在Haskell中使用工作池运行并行URL下载
我想使用Control.Concurrent.Async在Haskell中使用工作池运行并行URL下载,haskell,Haskell,我想使用Control.Concurrent.Asyncmapconcurrent与httpconcurrent执行并行下载。这个解决方案对于我的案例来说是不够的,因为我希望处理n个任务,但将并发工作者的数量限制为m,其中mconcurrent a。这就是由throttledAction组成的。为了导入构造函数,而不仅仅是类型,您需要编写import-Control.Concurrent.Async(Concurrent(Concurrent)、runconcurrent)或虚线速记import
mapconcurrent
与httpconcurrent
执行并行下载。这个解决方案对于我的案例来说是不够的,因为我希望处理n个任务,但将并发工作者的数量限制为m,其中mmap
多个m块是不够的,因为这样,活跃的工作人员的数量将趋向于少于m,因为一些任务将比其他任务更早完成,留下一个利用率差距
有没有一种简单的方法——几乎像我希望的同时使用MapConcurrent
一样简单——来实现一个工作池,同时执行一个任务队列,直到所有任务都完成
或者简单地保持Haskell并使用
xargs-p
进行流程级并行更容易吗?也许最简单的解决方案是在包装IO
操作之前,先使用一个辅助函数来限制它们:
withConc :: QSem -> (a -> IO b) -> (a -> Concurrently b)
withConc sem f = \a -> Concurrently
(bracket_ (waitQSem sem) (signalQSem sem) (f a))
我们可以结合使用withConc
对任务的任何Traversable
容器执行限制并发遍历:
traverseThrottled :: Int -> (a -> IO b) -> [a] -> IO [b]
traverseThrottled concLevel action tasks = do
sem <- newQSem concLevel
runConcurrently (traverse (withConc sem action) tasks)
我建议使用或并行交错
from。它(除其他外)具有这些属性
您可以为它使用
monad par
,它与async
一样,是由Simon Marlow制作的
例如:
import Control.Concurrent (threadDelay)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Par.Combinator (parMapM)
import Control.Monad.Par.IO (runParIO)
download :: Int -> IO String
download i = do
putStrLn ("downloading " ++ show i)
threadDelay 1000000 -- sleep 1 second
putStrLn ("downloading " ++ show i ++ " finished")
return "fake response"
main :: IO ()
main = do
-- "pooled" mapM
responses <- runParIO $ parMapM (\i -> liftIO $ download i) [1..10]
mapM_ putStrLn responses
代码也可通过获得,最新版本的具有用于池式并发的各种组合器
从另一个anwser改编Niklas的代码,您可以使用unlifio
像这样:
#!/usr/bin/env stack
-- stack --resolver lts-14.11 --install-ghc runghc --package unliftio --package say
{-# LANGUAGE OverloadedStrings #-}
import Control.Concurrent (threadDelay)
import Say
import UnliftIO.Async
download :: Int -> IO String
download i = do
sayString ("downloading " ++ show i)
threadDelay 1000000 -- sleep 1 second
sayString ("downloading " ++ show i ++ " finished")
return "fake response"
main :: IO ()
main = do
responses <- pooledMapConcurrentlyN 5 download [1 .. 5]
print responses
谢谢,这个答案看起来很棒。这些线程肯定比使用
xargs-P
启动多个Haskell进程要便宜,这非常有效。一个相关的新手问题:以某种方式仅导入异步中使用的函数/类型将不起作用,即导入控制.Concurrent.async(Concurrent,runconcurrent)将产生一些奇怪的错误,抱怨找不到并发。关于这个问题有什么线索吗?顺便说一句,concurrent看起来像一个函数,因为它点缀着throttleAction,但它有大写的起始字母?@KevinZhu在Haskell中,有些类型具有与该类型同名的值构造函数。构造函数以大写开头,可以用作函数。类型concurrent
具有值构造函数concurrent::IO a->concurrent a
。这就是由throttledAction
组成的。为了导入构造函数,而不仅仅是类型,您需要编写import-Control.Concurrent.Async(Concurrent(Concurrent)、runconcurrent)
或虚线速记import-Control.Concurrent.Async(Concurrent(..)
导入类型中定义的所有构造函数和访问器。@KevinZhu如果需要预打包的解决方案,则来自“unlifio”包的pooledMapConcurrentlyN
类似于traverseThrottled
,但使用线程池来限制并发性,而不是一次性生成所有线程,然后使用信号量。我的用法并不是真正针对N个内核上的N个工作线程的并行性,而是并发性,在这种情况下,HTTP请求可能需要随机的时间来完成,并且在等待网络时处理器处于空闲状态。parallel io
是否也适用于后一种情况?@dan是的,Pool
定义了要使用的工人数量,然后他们处理一个给定的n个任务的列表,这样每次m个工人都在执行。如果我使用并行io,我是否总是要用额外的标志-线程化并提供+RTS-N2-RTS
来编译它?这似乎有点烦人。@dan如果在运行时给出-RTS
不方便,您可以在编译时使用GHC的-with-rtsopts
选项进行设置。有点相关,所以问题是:您可以通过RTS参数以外的其他参数来控制并行量吗?@phadej对于这种情况,Petr用并行io的回答似乎很合适。
downloading 10
downloading 9
downloading 9 finished
downloading 10 finished
downloading 8
downloading 7
downloading 8 finished
downloading 7 finished
downloading 6
downloading 5
downloading 6 finished
downloading 5 finished
downloading 4
downloading 3
downloading 4 finished
downloading 3 finished
downloading 2
downloading 1
downloading 2 finished
downloading 1 finished
fake response
fake response
fake response
fake response
fake response
fake response
fake response
fake response
fake response
fake response
#!/usr/bin/env stack
-- stack --resolver lts-14.11 --install-ghc runghc --package unliftio --package say
{-# LANGUAGE OverloadedStrings #-}
import Control.Concurrent (threadDelay)
import Say
import UnliftIO.Async
download :: Int -> IO String
download i = do
sayString ("downloading " ++ show i)
threadDelay 1000000 -- sleep 1 second
sayString ("downloading " ++ show i ++ " finished")
return "fake response"
main :: IO ()
main = do
responses <- pooledMapConcurrentlyN 5 download [1 .. 5]
print responses
$ stack pooled.hs
downloading 1
downloading 2
downloading 3
downloading 4
downloading 5
downloading 1 finished
downloading 5 finished
downloading 2 finished
downloading 3 finished
downloading 4 finished
["fake response","fake response","fake response","fake response","fake response"]