与Haskell中的工作池并行运行命令调用超时
我必须编写一个命令行工具,将一些组件粘在一起进行实验,并需要帮助进行代码设计,以满足我的需求 在顶层,我必须处理每个由一个对另一个程序调用函数“System.Process.readProcessWithExitCode”生成的样本—在运行时以及内存消耗中—代价高昂。因此,您可以想象有一个(昂贵的)函数“genSample::IO a”,并且需要n个该函数的返回值 我的要求是: 1.设p为处理器数量,则最多应并行计算p个样本(即对genSample的调用)。 2.应该可以设置一个超时,以中止样本的生成。 3.如果所有样本的计算超时,则应停止genSample调用中启动的进程 我当前的解决方案满足要求1和2。对于第三个,我目前通过执行killall命令来帮助自己。对我来说,这似乎是一个卑鄙的手段。也许有人有更好的主意 这里是我当前解决方案的核心部分:与Haskell中的工作池并行运行命令调用超时,haskell,parallel-processing,timeout,Haskell,Parallel Processing,Timeout,我必须编写一个命令行工具,将一些组件粘在一起进行实验,并需要帮助进行代码设计,以满足我的需求 在顶层,我必须处理每个由一个对另一个程序调用函数“System.Process.readProcessWithExitCode”生成的样本—在运行时以及内存消耗中—代价高昂。因此,您可以想象有一个(昂贵的)函数“genSample::IO a”,并且需要n个该函数的返回值 我的要求是: 1.设p为处理器数量,则最多应并行计算p个样本(即对genSample的调用)。 2.应该可以设置一个超时,以中止样本
import qualified Control.Monad.Par.Class as ParIO
import qualified Control.Monad.Par.IO as ParIO
…
-- | @parRepeatM i n a@ performs action @a@ @n@ times in parallel with timeout @t@
parRepeatM :: ParIO.NFData a =>
Integer -- ^ timeout in seconds
-> Integer -- ^ number of duplicates (here: number of req. samples)
-> IO a -- ^ action to perform (here: genSample)
-> IO (Maybe [a])
parRepeatM t n a = timeout t $ ParIO.runParIO $ do
let tasks = genericReplicate n $ liftIO a -- :: [ParIO a]
ivars <- mapM ParIO.spawn tasks
mapM ParIO.get ivars
<代码>导入合格的控件.MunAD.PAR.类为PARIO
导入合格的控件MunAD.PAR.IO为PARIO
…
--|@parRepeatM i n a@执行动作@a@@n@次,同时执行超时@t@
parRepeatM::ParIO.NFData=>
整数--^超时(秒)
->整数--^重复数(此处:请求样本数)
->IO a--^要执行的操作(此处:genSample)
->IO(可能[a])
parRepeatM t n a=超时t$ParIO.runParIO$do
让tasks=genericrepplicate n$liftIO a--::[ParIO a]
ivars在Haskell中,通常通过异步异常处理取消。这就是
timeout
的用法
因此,我们可以尝试在执行外部进程的代码中安装异常处理程序。每当出现异常(异步或非异步)时,处理程序将调用terminateProcess
。因为需要引用
进程句柄,我们将不得不使用,而不是更高级别的
首先,一些导入和辅助函数(我正在使用这个包):
此函数启动外部进程并返回其标准输出和退出代码,如果线程被取消,则终止该进程:
safeExec :: CreateProcess -> IO (B.ByteString, ExitCode)
safeExec cp =
bracketOnError
(createProcess cp {std_out = CreatePipe})
(\(_,_ ,_,pHandle) -> terminateCarefully pHandle)
(\(_,Just hOut,_,pHandle) -> do
-- Workaround for a Windows issue.
latch <- newEmptyMVar
race'
(do -- IO actions are uninterruptible on Windows :(
takeMVar latch
contents <- B.hGetContents hOut
ec <- waitForProcess pHandle
pure (contents,ec))
-- Dummy interruptible action that
-- receives asynchronous exceptions first
-- and helps to end the other action.
(onException
(do
putMVar latch ()
-- runs forever unless interrupted
runConcurrently empty)
(terminateCarefully pHandle)))
calc应用程序在三秒后被杀死。这是全部
还请记住:
在Windows上,如果进程是由createProcess with shell创建的shell命令,或由runCommand或runInteractiveCommand创建的,则terminateProcess将仅终止shell,而不是命令本身
首先,非常感谢你的回答。该解决方案的形状与我的目的所需的形状相似。我试图使它适应我的需要,并以粘贴在这里的代码结束:不幸的是,对我来说,程序(这里是为了测试目的的“睡眠”——但我真正的计算密集型程序的行为相同)似乎没有被调用。你知道为什么吗?如果不需要windows的可移植性,事情会变得更简单吗?@user2292040您是否正在使用
System.IO
中的hGetContents
?如果使用Data.ByteString
中的可选hGetContents
或Data.Text.IO
(Text
package)中的可选hGetContents
,问题是否仍然存在?我发现了:我必须将输入赋予“createProcess”给定的输入句柄。我的程序已被调用,但由于缺少输入而完全空闲:-D我将努力改进我的解决方案——同时附上我最近从中找到的答案,如果成功,请将答案写在这里。再次感谢您的帮助,让我走上了正确的道路。@user2292040不客气。您应该采取的一个预防措施是在读取stdout的同时执行stdin的“馈送”,同时读取stderr。否则,由于输出缓冲区已满且从未读取,可能会发生死锁。实现这一点的最简单方法是同时使用异步
包中的应用程序。
safeExec :: CreateProcess -> IO (B.ByteString, ExitCode)
safeExec cp =
bracketOnError
(createProcess cp {std_out = CreatePipe})
(\(_,_ ,_,pHandle) -> terminateCarefully pHandle)
(\(_,Just hOut,_,pHandle) -> do
-- Workaround for a Windows issue.
latch <- newEmptyMVar
race'
(do -- IO actions are uninterruptible on Windows :(
takeMVar latch
contents <- B.hGetContents hOut
ec <- waitForProcess pHandle
pure (contents,ec))
-- Dummy interruptible action that
-- receives asynchronous exceptions first
-- and helps to end the other action.
(onException
(do
putMVar latch ()
-- runs forever unless interrupted
runConcurrently empty)
(terminateCarefully pHandle)))
main :: IO ()
main = do
race_ (safeExec $ proc "calc" [])
(threadDelay (3*10^6))