这"automatic parallelism"例外安全吗?



我试图在Haskell中实现某种自动并行。我的想法是:

  1. 获取当前的并行能力。(最初,这是处理器的数量。(

  2. 当任务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或类似选项运行。

最新更新