ReadMVar не просыпается на putMVar

Мой код, кажется, зависает на 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? В старых версиях readMVar представляет собой takeMVar, за которым следует putMVar (не выполняется атомарно). Если tryTakeMVar и второй putMVar произошли между ними, это объяснило бы поведение, которое вы наблюдаете.

Daniel Wagner 10.01.2019 01:30

Я верю GHC 8.6.3, хотя при использовании Stack это немного сложно сказать. Однако, учитывая ваше ясное объяснение, я постараюсь немного подробнее узнать, какая версия используется.

crockeea 10.01.2019 01:48

Подтвердил, что это GHC 8.6.3.

crockeea 10.01.2019 01:53

Вот проблема: я использую строгий параллелизм, который не поддерживает tryReadMVar. В результате я реализовал это сам, используя tryTakeMVar и putMVar (неатомарно). Таким образом, то, что говорит Даниил, верно.

crockeea 10.01.2019 02:12

Вы должны написать это как ответ. Это хорошая работа по отслеживанию.

Carl 10.01.2019 04:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
5
90
1

Ответы 1

Я переключил свою реализацию 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 принта "поставил м0"
  • поток 1 принимает mvar, используя tryTakeMVar в tryReadMVar
  • поток 2 принимает mvar с помощью tryTakeMVar
  • thread2 отпечатки "взять м"
  • thread2 помещает mvar с помощью putMVar
  • thread2 печатает "положить m1"
  • тупиковый поток 1 при попытке putMVar внутри tryReadMVar

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

Я создал проблему github.com/ygale/strict-concurrency/issues/2 с strict-concurrency, запрашивающую добавление tryReadMVar, чтобы другие люди не совершили ту же ошибку.

crockeea 15.01.2019 18:37

Другие вопросы по теме