我试图在Haskell中实现某种自动并行。我的想法是:
-
获取当前的并行能力。(最初,这是处理器的数量。(
-
当任务a被要求与任务B并行运行时:
-
如果容量为1,则依次执行A和B。
-
否则,将能力减少1,然后与B并行执行A。当A完成时,将能力增加1。
我实现了这样的想法:
import Control.Concurrent
import Control.Exception
pForkIO :: IO a -> IO a
pForkIO action = do
cap <- getNumCapabilities
if (1 == cap)
then action
else runInBoundThread (bracket_ (setNumCapabilities (cap - 1)) (do {cap2 <- getNumCapabilities; setNumCapabilities (cap2 + 1)}) action)
pForkIO
:的使用示例
import Data.List
pFoldMap :: Monoid m => (a -> m) -> [a] -> IO m
pFoldMap f xs = go f xs (length xs) where
go _ [] _ = mempty
go f [x] _ = return (f x)
go f xs n = do
let halfN = quot n 2
let (us,vs) = splitAt halfN xs
m1 <- pForkIO (go f us halfN)
m2 <- go f vs (n - halfN)
return (m1 <> m2)
为了处理异常,我使用了bracket_
。pForkIO
真的异常安全吗?
(顺便说一句,由于未知原因,尽管我的机器有8个处理器,但getNumCapabilities
最初返回1。这应该是另一个问题…(
编辑:是的,我知道,上面的pFoldMap
比普通的foldMap
更复杂,但现在让我们关注pForkIO
。。。
这不会按您想要的方式工作。
首先,getNumCapabilities
/setNumCapabilities
函数不管理可以";分配";手动方式。相反,它们基本上获取并设置-N
运行时参数的当前设置,该参数指定由运行时系统管理的将运行Haskell代码的同时CPU可运行线程的总数。如果您使用setNumCapabilities
来减少功能的数量,那么您只是减少了实际并行运行的线程数量(绑定与否(。
其次,runInBoundThread
不并行派生线程,它只是确保操作在绑定线程内运行。如果这需要创建一个新的绑定线程,那么在操作完成之前,该操作仍将在调用阻塞的情况下按顺序运行。
如果您希望派生并并行运行多个绑定线程(最多不超过功能数量(,则需要单独管理由getNumCapabilities
中的值初始化的功能计数的副本(例如,使用MVar
(,但根本不应该使用setNumCapabilities
。并且,您需要使用forkOS
(派生绑定线程(来代替runInBoundThread
。
不过,总的来说,我认为这样做没有任何意义。运行时系统已经支持使用forkIO
并行运行一组IO线程,并且它将在单独的内核上同时运行多达getNumCapabilities
的IO线程。使用绑定线程在很大程度上是适得其反的,除非它们是使用管理线程加载状态的外部API所需要的;而且将分叉线程的数量限制在功能的数量通常会适得其反,除非在某些特定的应用程序中。
此外,getNumCapability
可能会为您返回1
,因为这是RTS-N
标志的默认值。如果要并行运行多个线程,则应确保使用-threaded
运行时进行了编译,并使用RTS选项+RTS -N
(没有数字,设置为物理功能的数量(或+RTS -N4
或类似选项运行。