readMVar не просыпается на putMVar - PullRequest
0 голосов
/ 10 января 2019

Мой код, кажется, висит на readMVar после того, как другой поток вызывает putMVar. Я бы не ожидал, что это произойдет, но это то, что я наблюдаю. Мой основной поток создает два новых потока, каждый из которых имеет доступ к общему MVar м.

Тема 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"

Main:

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, но моя программа просто зависает.

1 Ответ

0 голосов
/ 10 января 2019

Я переключил свою реализацию 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.

Одновременно тупик возник в следующей ситуации (которую описывает Даниил):

  • нить 1 печатает "галочку"
  • поток 2 помещает mvar, используя putMVar
  • нить 2 отпечатка "put m0"
  • нить 1 принимает mvar, используя tryTakeMVar в пределах tryReadMVar
  • нить 2 принимает mvar, используя tryTakeMVar
  • thread2 печатает "take m"
  • thread2 помещает mvar, используя putMVar
  • thread2 печатает "put m1"
  • поток 1 блокируется при попытке putMVar в пределах tryReadMVar

Оказывается, что наличие атома tryReadMVar полезно!

...