我正在尝试在 Haskell 中使用并发性进行特定的优化,其中只需要两个值中的一个,并且根据情况,任何一个的创建速度可能比另一个快得多。
我以为我可以用 forkIO 运行 2 个线程,然后等到一个值被放入 MVar 中。这是我为此编写的一个简单的测试:
import Control.Concurrent
main = do out <- newEmptyMVar
t1 <- forkIO (makeString out)
t2 <- forkIO (makeInt out)
v <- takeMVar out
killThread t1
killThread t2
case v of
Left s -> putStrLn s
Right i -> putStrLn $ show i
makeString out = do s <- return ( show (primes !! 10000))
putMVar out $ Left s
makeInt out = do i <- return 2
putMVar out $ Right i
primes = sieve [2..]
where sieve (x:xs) = x : (sieve $ filter ((/=0).(flip mod x)) xs)
编译方式:
ghc --make -threaded Test
但是,只有 Left s 的情况被达到,尽管获得素数应该需要足够长的时间才能启动 makeInt 线程(并且返回 2 真的不应该花费那么多时间)。为什么会这样,我该如何解决这个问题?
这里的问题是懒惰。makeString
只是插入一个thunk来计算show (primes !! 10000)
,然后由主线程评估。插入 thunk 非常快,因此在这种情况下它恰好赢得了比赛。
要强制在线程内进行计算,您可以将return
更改为evaluate
:
makeString out = do s <- evaluate $ show (primes !! 10000)
putMVar out $ Left s
在大多数情况下,这应该会导致makeInt
赢得比赛(尽管不能保证)。
是的,线程确实是不确定的(在 GHC 中)。
碰巧您的特定代码以 t1 始终获胜的方式进行结构化和优化。没有保证。
如果您想尝试按摩它以产生不同的结果,请尝试打开优化 ( -O2
) 和/或使用多个内核 ( +RTS -N
)。
例如,在我的机器上,连续运行两次:
$ ghc -O2 -threaded --make A.hs -rtsopts -fforce-recomp
[1 of 1] Compiling Main ( A.hs, A.o )
Linking A.exe ...
$ ./A +RTS -N2
2
$ ./A +RTS -N2
104743
正如 hammar 指出的那样,您还可以构建代码以强制在线程中进行更多工作(或切换到使用严格的 mvar)。