在另一个线程调用 readMVar 之后,我的代码似乎卡在 putMVar 上。我不希望这种情况发生,但这就是我正在观察的。我的主线程创建了两个新线程,每个线程都可以访问共享的 MVar m。

主题 1:

do
  putStrLn "tick"
  x <- readMVar m
  putStrLn "tock"

主题 2:
do
  putMVar m 0
  putStrLn "put m0"
  void $ tryTakeMVar m
  putStrLn "take m"
  putMVar m 1
  putStrLn "put m1"

主要的:
do
  m <- newEmptyMVar
  <start thread 1>
  <start thread 2>

在以下场景中,我的程序挂起:

两个线程可以访问一个共享的 MVar m ,它最初是空的。线程 1 阻塞 readMVar m 。同时,线程 2 调用 putMVar m ... 。此时,线程 1 可以继续,但我们假设它没有。然后线程 2 调用 tryTakeMVar m ,这可能会清空一个完整的 MVar 。然后线程 2 再次调用 putMVar m ... 。此场景对应于以下输出:
tick
put m0
take m
put m1
<hang>

这里发生了什么?我希望“tock”应该打印,因为线程 2 填充了 MVar,但我的程序只是挂起。

最佳答案

在尝试调试空间泄漏时,我将 MVar 实现从 base 切换到 strict-concurrency。但正如问题所示,我的代码使用 tryReadMVar ,出于某种原因,strict-concurrency 未提供。因此,不久前,我自己实现了 tryReadMVar 如下:

tryReadMVar :: (NFData a) => MVar a -> IO (Maybe a)
tryReadMVar m = do
  mm <- tryTakeMVar m
  case mm of
    Nothing -> return ()
    Just a -> putMVar m a
  return mm

没有真正考虑影响。从那以后我就忘记了做这件事。正如丹尼尔指出的那样,旧版本的 base 曾经做过类似的事情,但新版本有一个原子 tryReadMVar 实现。因此,即使我使用的是新版本的 GHC,由于使用 strict-concurrency ,问题还是被重新引入。

同时,死锁发生在以下情况(Daniel 描述):
  • 线程 1 打印“tick”
  • 线程 2 使用 putMVar
  • 放置 mvar
  • 线程 2 打印“put m0”
  • 线程 1 在 tryTakeMVar
  • 中使用 tryReadMVar 获取 mvar
  • 线程 2 使用 tryTakeMVar
  • 获取 mvar
  • thread2 打印“take m”
  • thread2 使用 putMVar
  • 放置 mvar
  • thread2 打印“put m1”
  • 线程 1 在尝试在 putMVar
  • 中执行 tryReadMVar 时出现死锁

    事实证明,拥有一个原子的 tryReadMVar 很有用!

    关于multithreading - readMVar 不会在 putMVar 上唤醒,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54120390/

    10-09 22:53