gpt4 book ai didi

multithreading - Haskell 计算密集型线程阻塞所有其他线程

转载 作者:行者123 更新时间:2023-12-02 02:02:20 28 4
gpt4 key购买 nike

我想编写一个程序,其主线程 fork 一个新线程进行计算并等待它完成一段时间。如果子线程没有在给定时间内完成,它就会超时并被杀死。我有以下代码。

import Control.Concurrent

fibs :: Int -> Int
fibs 0 = 0
fibs 1 = 1
fibs n = fibs (n-1) + fibs (n-2)

main = do
mvar <- newEmptyMVar
tid <- forkIO $ do
threadDelay (1 * 1000 * 1000)
putMVar mvar Nothing
tid' <- forkIO $ do
if fibs 1234 == 100
then putStrLn "Incorrect answer" >> putMVar mvar (Just False)
else putStrLn "Maybe correct answer" >> putMVar mvar (Just True)
putStrLn "Waiting for result or timeout"
result <- takeMVar mvar
killThread tid
killThread tid'

我用 ghc -O2 Test.hs 编译了上面的程序和 ghc -O2 -threaded Test.hs并运行它,但在这两种情况下程序只是挂起而不打印任何东西或退出。如果我添加一个 threadDelay (2 * 1000 * 1000)if 之前的计算线程 block 然后程序按预期工作并在一秒钟后完成,因为计时器线程能够填充 mvar .

为什么线程没有按我的预期工作?

最佳答案

GHC 在其并发实现中使用了一种协作式和抢占式多任务处理的混合体。

在 Haskell 级别,它似乎是抢占式的,因为线程不需要显式地让出,并且似乎可以随时被运行时中断。但是在运行时级别,线程在分配内存时会“屈服”。由于几乎所有 Haskell 线程都在不断地分配,这通常工作得很好。

但是,如果可以将特定计算优化为非分配代码,则它可能在运行时级别变得不合作,因此在 Haskell 级别不可抢占。正如@Carl 指出的那样,它实际上是 -fomit-yields -O2 隐含的标志允许这种情况发生:

-fomit-yields

Tells GHC to omit heap checks when no allocation is being performed. While this improves binary sizes by about 5%, it also means that threads run in tight non-allocating loops will not get preempted in a timely fashion. If it is important to always be able to interrupt such threads, you should turn this optimization off. Consider also recompiling all libraries with this optimization turned off, if you need to guarantee interruptibility.



显然,在单线程运行时(没有 -threaded 标志),这意味着一个线程可以完全饿死所有其他线程。不太明显的是,即使您使用 -threaded 进行编译,也会发生同样的事情。并使用 +RTS -N选项。问题是不合作的线程可能会耗尽运行时 调度器本身。如果在某个时刻,不合作线程是当前计划运行的唯一线程,它将变得不可中断,并且调度程序将永远不会重新运行以考虑调度其他线程,即使它们可以在其他 O/S 线程上运行。

如果您只是想测试一些东西,请更改 fib 的签名至 fib :: Integer -> Integer .由于 Integer导致分配,一切都将重新开始工作(有或没有 -threaded )。

如果你在实际代码中遇到这个问题,目前最简单的解决方案是@Carl 建议的:如果你需要保证线程的可中断性,代码应该编译成 -fno-omit-yields。 ,它将调度程序调用保持在非分配代码中。根据文档,这会增加二进制大小;我认为它也会带来很小的性能损失。

或者,如果计算已经在 IO , 然后明确地 yield 在优化循环中学习可能是一个好方法。对于纯计算,您可以将其转换为 IO 和 yield ,尽管通常您可以找到一种简单的方法来再次引入分配。在大多数现实情况下,会有一种方法只引入“少数” yield s 或分配——足以使线程再次响应但不足以严重影响性能。 (例如,如果您有一些嵌套的递归循环, yield 或在最外层循环中强制分配。)

关于multithreading - Haskell 计算密集型线程阻塞所有其他线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59987940/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com