Мой код, кажется, зависает на 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, но моя программа просто зависает.
Я верю GHC 8.6.3, хотя при использовании Stack это немного сложно сказать. Однако, учитывая ваше ясное объяснение, я постараюсь немного подробнее узнать, какая версия используется.
Подтвердил, что это GHC 8.6.3.
Вот проблема: я использую строгий параллелизм, который не поддерживает tryReadMVar. В результате я реализовал это сам, используя tryTakeMVar и putMVar (неатомарно). Таким образом, то, что говорит Даниил, верно.
Вы должны написать это как ответ. Это хорошая работа по отслеживанию.





Я переключил свою реализацию 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.
Одновременно с этим возникла тупиковая ситуация в следующей ситуации (которую описывает Даниэль):
putMVartryTakeMVar в tryReadMVartryTakeMVarputMVarputMVar внутри tryReadMVarОказывается, наличие атомарного tryReadMVar полезно!
Я создал проблему github.com/ygale/strict-concurrency/issues/2 с strict-concurrency, запрашивающую добавление tryReadMVar, чтобы другие люди не совершили ту же ошибку.
Какая версия GHC? В старых версиях
readMVarпредставляет собойtakeMVar, за которым следуетputMVar(не выполняется атомарно). ЕслиtryTakeMVarи второйputMVarпроизошли между ними, это объяснило бы поведение, которое вы наблюдаете.