与Haskell中的工作池并行运行命令调用超时

与Haskell中的工作池并行运行命令调用超时,haskell,parallel-processing,timeout,Haskell,Parallel Processing,Timeout,我必须编写一个命令行工具,将一些组件粘在一起进行实验,并需要帮助进行代码设计,以满足我的需求 在顶层,我必须处理每个由一个对另一个程序调用函数“System.Process.readProcessWithExitCode”生成的样本—在运行时以及内存消耗中—代价高昂。因此,您可以想象有一个(昂贵的)函数“genSample::IO a”,并且需要n个该函数的返回值 我的要求是: 1.设p为处理器数量,则最多应并行计算p个样本(即对genSample的调用)。 2.应该可以设置一个超时,以中止样本

我必须编写一个命令行工具,将一些组件粘在一起进行实验,并需要帮助进行代码设计,以满足我的需求

在顶层,我必须处理每个由一个对另一个程序调用函数“System.Process.readProcessWithExitCode”生成的样本—在运行时以及内存消耗中—代价高昂。因此,您可以想象有一个(昂贵的)函数“genSample::IO a”,并且需要n个该函数的返回值

我的要求是: 1.设p为处理器数量,则最多应并行计算p个样本(即对genSample的调用)。 2.应该可以设置一个超时,以中止样本的生成。 3.如果所有样本的计算超时,则应停止genSample调用中启动的进程

我当前的解决方案满足要求1和2。对于第三个,我目前通过执行killall命令来帮助自己。对我来说,这似乎是一个卑鄙的手段。也许有人有更好的主意

这里是我当前解决方案的核心部分:

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))