部分

时间:2018-06-14 03:32:16

标签: haskell asynchronous concurrency

我有一个表示shell命令的字符串列表,我想同时执行10个,例如10个。这是我第一次真正涉足Haskell的并发编程,我遇到了一些麻烦。

我的想法是将列表分成十个元素的子列表,然后mapM_一个函数同时生成十个命令中的每一个,等待每个命令完成,然后再转到下一组十。然而,我使用/采用的每个不同的库/方法似乎只是同时启动列表中的每个命令。

假设我有一个最简单的函数来执行shell命令:

import System.Process    

execShellCommand :: String -> IO ()
execShellCommand cmd = void $ createProcess (shell cmd)

天真的方法

import Control.Monad
import Data.List.Split.Internals

runChunks :: [String] -> IO ()
runChunks as = mapM_ (mapM_ execShellCommand) $ chunksOf 10 as

一次执行列表中的所有命令(我尝试使用Conduit.Process中的waitFor函数,结果相似)。让我们尝试更加周到,并重新定义函数execShellCommand以使用Control.Concurrent

import Control.Concurrent

execShellCommand :: String -> IO ()
execShellCommand cmd = do
  m <- newEmptyMVar
  r <- void $ createProcess (shell cmd)
  forkIO $ putMVar m r
  takeMVar m

runChunks :: [String] -> IO ()
runChunks [] = return ()
runChunks as = do
  mapM_ execShellCommand $ take 10 as
  runChunks $ drop 10 as

也做同样的事情(一次运行列表中的所有命令)。另一个具有相同结果的图书馆:

import Control.Concurrent.Async

runChunks :: [String] -> IO ()
runChunks [] = return ()
runChunks as = do
  ck <- mapM (async . execShellCommand) $ take 10 as
  mapM_ wait ck
  runChunks $ drop 10 as

我要么不在这里抓住一个概念,在这种情况下,我非常欣赏澄清的解释,或者我只是没有看到能够实现我瞄准的效果的库函数对于;对于后者,一个例子将是非常有帮助的。非常感谢你。

1 个答案:

答案 0 :(得分:2)

我对你使用waitFor尝试的内容感到有点好奇。这是我的第一次尝试:

import System.Process

commands = ["./demo.sh " ++ show n | n <- [1..10]]

chunksOf _ [] = []
chunksOf n xs = take n xs : chunksOf n (drop n xs)

spawn cmd = do
  (_,_,_,proc) <- createProcess (shell cmd)
  return proc

spawnAndAwaitGroup cmds = do
  procs <- mapM spawn cmds
  mapM_ waitForProcess procs

main = do
  mapM_ spawnAndAwaitGroup (chunksOf 3 commands)

注意IO动作是如何组成的;等待需要适应生成,所以我们在块级别应用这两个操作。我们可以集体创建spawn个动作,但waitForProcess需要返回的句柄。因此,我使用了mapMdo <-

demo.sh

中使用此功能进行测试
#!/bin/sh
echo $1 starting
sleep 3
echo $1 ending

输出:

2 starting
1 starting
3 starting
3 ending
1 ending
2 ending
4 starting
5 starting
6 starting
4 ending
5 ending
6 ending
7 starting
8 starting
9 starting
7 ending
8 ending
9 ending
10 starting
10 ending

值得注意的是,这是一个纯粹的顺序程序,而不是Haskell级别的并发程序。它等同于这个shell脚本:

./demo.sh 1 &
./demo.sh 2 &
./demo.sh 3 &
wait
./demo.sh 4 &
./demo.sh 5 &
./demo.sh 6 &
wait
./demo.sh 7 &
./demo.sh 8 &
./demo.sh 9 &
wait
./demo.sh 10 &
wait

所有并发行为都发生在操作系统级别,在生成的进程的交互中。