Haskell - 关于System.Process和多线程的一些问题

4

我有一个用C语言写成的小型数字模拟程序(我必须用C语言编写以与我的导师分享),但我想使用类似“Haskell脚本”的东西来组织模拟。该程序接受一些命令行参数并输出一些内容,我想将其重定向到一个文件中,因此我做了如下操作:

 import Control.Monad
 import System.Process

我有一个创建输出文件名称的函数:

filename :: Int -> String  
filename n = some stuff here...

我想要运行的命令是:

command :: Int -> String
command n = "./mycutesimulation " ++ show n ++ " >" ++ filename n

最后,我列出要运行的任务列表,并使用 runCommand 命令来运行它们:

commands = map command [1,2..1000]

main = do
   sequence_ $ map runCommand commands

问题在于,我运行这个“脚本”后,我的电脑几乎会因为负载而冻结。正在执行的程序在内存使用方面非常轻便,并且在一瞬间就能运行。这不应该发生。
所以,我的问题是:
1)我刚才是把1000个进程同时执行了吗?如何按顺序执行它们——顺序执行或每次只执行几个进程?
2)我正在四核处理器上运行它,如果能利用这个优势就太好了。有没有办法使用 -threaded 标志编译它,并以有组织的方式并发执行进程?
3个回答

4
你需要一个waitForProcess =<< runCommand
import System.Process

main = sequence $ map (\x -> runCommand x) commands
 where commands = map (\x -> "echo " ++ show x) [1, 2..1000]

症状与您的相似,但是

import System.Process

main = sequence $ map (\x -> waitForProcess =<< runCommand x) commands
 where commands = map (\x -> "echo " ++ show x) [1, 2..1000]

工作。


3
首先,您应该检查顶部或任务管理器,以查看是否确实在快速连续创建1000个进程,然后基于此寻找解决方案。
减缓进程创建的简单方法是在创建下一个进程之前等待每个进程完成。因此,您应该将自己的函数映射到您的命令上,而不是将runCommand映射到commands上,该函数首先调用runCommand,然后在返回的ProcessHandle上调用waitForProcess,即每次调用您的辅助函数都会阻塞,直到生成的进程完成。
以上解决方案的缺点是它只使用了您的四个核心中的一个。因此,您可以将commands分成四个(或您想要使用的核心数),然后为每个子列表生成四个工作线程,并对每个子列表运行map。
另外,mapM_ f == sequence_ . map f.

谢谢指出 forkIO。我按照你的建议成功地实现了并发运行。非常好!这是我的第一个多核程序!哈哈哈... - Rafael S. Calsaverini
你的最终旁白无法输入; mapM_ = (.) sequence_ . map 更为准确。 - ephemient

1

如果有帮助的话,这里是一个快速而简单的“一次运行几个”的方法:

import System.Process

commands = replicate 16 "sleep 2"

runSome handles cmd = do
    (h:hs) <- handles
    waitForProcess h
    h' <- runCommand cmd
    return $ hs ++ [h']

test n = 
    let initial = mapM runCommand $ take n commands
    in foldl runSome initial (drop n commands)

这只是将列表误用为简单队列,按照您指定的数量运行尽可能多的命令,然后等待队列前面的命令完成并添加新命令。请注意,如果混合了一些长时间运行的命令,则此方法可能无法完美地工作,但对您来说可能已足够。请不要认为这是“正确”的做法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接