在Haskell中使用工作池运行并行URL下载

时间:2015-03-19 21:09:31

标签: haskell

我想使用Control.Concurrent.Async mapConcurrently来执行http-conduit的并行下载。解决方案here对我的情况来说还不够,因为我想处理 n 任务,但是将并发工作者的数量限制为 m ,其中< em> m &lt; 名词的。

传递给{{1>} m 的多个块是不够的,因为那时活跃工作者的数量往往会小于 m 因为某些任务比其他任务更早完成,留下了利用率差距。

是否有一种简单的方法 - 几乎和使用mapConcurrently一样简单 - 我希望 - 实现一个工作池同时执行任务队列,直到完成所有任务?

或者仅仅保持Haskell简单并使用mapConcurrently进行流程级并行化更容易吗?

4 个答案:

答案 0 :(得分:4)

也许最简单的解决方案是使用semaphore使用Concurrently进行限制,然后使用这样的辅助函数将它们包装在traverse中:

IO

我们可以将withConc :: QSem -> (a -> IO b) -> (a -> Concurrently b) withConc sem f = \a -> Concurrently (bracket_ (waitQSem sem) (signalQSem sem) (f a)) 与{{3}}结合使用来执行任何withConc任务容器的限制并发遍历:

Traversable

这种方法的一个缺点是使用traverseThrottled :: Int -> (a -> IO b) -> [a] -> IO [b] traverseThrottled concLevel action tasks = do sem <- newQSem concLevel runConcurrently (traverse (withConc sem action) tasks) 将创建与任务一样多的线程,并且由于信号量的原因,它们中只有一部分将在任何给定时间执行实际工作。

另一方面,Haskell中的线程很便宜所以我认为在任务数量不是很大的情况下这是一个可以接受的解决方案。

修改:为Concurrently提供更一般的签名:

traverseThrottled

答案 1 :(得分:2)

我建议您使用parallel中的parallel-ioparallelInterleaved。它(以及其他)具有这些属性;

  
      
  1. 从不创建比在池中指定的更多或更少的未阻塞线程。注意:此计数包括并行执行的线程。这应该最大限度地减少争用,从而减少先发制人,同时也可以防止饥饿。
  2.   
  3. 返回时,已执行所有操作。
  4.   
  5. 只要所有操作都已执行,该功能就会及时返回。
  6.   

答案 2 :(得分:1)

您可以使用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

使用ghc --make -threaded PooledMapM.hs进行编译,运行方式如./PooledMapM +RTS -N2

你会看到输出:

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

代码也可通过https://gist.github.com/nh2/bfa3f182da9d13baa536

获取

答案 3 :(得分:0)

unliftio的最新版本具有用于合并并发的各种组合器。

要从另一个anwser改编Niklas的代码,您可以使用unliftio 像这样:

#!/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"]