readMVar不会在putMVar上唤醒

cro*_*eea 4 multithreading haskell ghc

我的代码似乎挂在readMVar另一个线程调用之后putMVar.我不指望这会发生,但这就是我所观察到的.我的主线程创建了两个新线程,每个线程都可以访问共享MVarm.

线程1:

do
  putStrLn "tick"
  x <- readMVar m
  putStrLn "tock"
Run Code Online (Sandbox Code Playgroud)

线程2:

do
  putMVar m 0
  putStrLn "put m0"
  void $ tryTakeMVar m
  putStrLn "take m"
  putMVar m 1
  putStrLn "put m1"
Run Code Online (Sandbox Code Playgroud)

主要:

do
  m <- newEmptyMVar
  <start thread 1>
  <start thread 2>
Run Code Online (Sandbox Code Playgroud)

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

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

tick
put m0
take m
put m1
<hang>
Run Code Online (Sandbox Code Playgroud)

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

cro*_*eea 5

我转向从我的无功实施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
Run Code Online (Sandbox Code Playgroud)

没有真正考虑其影响.从那以后,我一直在努力做到这一点.正如丹尼尔指出的那样,旧版本base用于做类似的事情,但较新的版本具有原子tryReadMVar实现.因此即使我使用的是新版本的GHC,这个问题也会因使用而重新引入strict-concurrency.

同时,死锁发生在以下情况(Daniel描述):

  • 线程1打印"tick"
  • 线程2使用mvar putMVar
  • 线程2打印"put m0"
  • 线程1使用tryTakeMVar内部的mvartryReadMVar
  • 线程2使用mvar tryTakeMVar
  • thread2打印"take m"
  • thread2使用mvar putMVar
  • thread2打印"put m1"
  • 尝试进入putMVar内部时线程1死锁tryReadMVar

结果证明拥有原子tryReadMVar是有用的!