Control.Concurrent.MVar
| Авторские права | (с) Университет Глазго 2001 |
|---|---|
| Лицензия | BSD-стиль (см. файл libraries/base/LICENSE) |
| Поддерживающий | libraries@haskell.org |
| Стабильность | стабильная |
| Переносимость | непереносимая (конкурентность) |
| Безопасный Haskell | Безопасный |
| Язык | Haskell2010 |
Содержание
Описание
MVar t — это изменяемая область памяти, которая либо пуста, либо содержит значение типа t. Она имеет две основные операции: putMVar, которая заполняет MVar если он пуст и блокирует в противном случае, и takeMVar, которая очищает MVar если он заполнен и блокирует в противном случае. Их можно использовать различными способами:
- В качестве синхронизированных изменяемых переменных,
- В качестве каналов, с
takeMVarиputMVarв качестве приёма и отправки, и - В качестве двоичного семафора
MVar (), сtakeMVarиputMVarв качестве ожидания и сигнала.
Они были представлены в статье "Concurrent Haskell" Саймона Пейтона Джонса, Эндрю Гордона и Сигбьёрна Финна, хотя некоторые детали их реализации с тех пор изменились (в частности, запись в заполненный MVar раньше вызывала ошибку, но теперь просто блокирует.)
Применимость
MVar предлагают больше гибкости, чем IORef, но меньше гибкости, чем STM. Они подходят для создания примитивов синхронизации и выполнения простого межпоточного обмена данными; однако они очень просты и подвержены гонкам, тупикам или необработанным исключениям. Не используйте их, если вам нужно выполнить более сложные атомарные операции, такие как чтение из нескольких переменных: используйте STM вместо этого.
В частности, «более крупные» функции в этом модуле (swapMVar, withMVar, modifyMVar_ и modifyMVar) — это просто композиция takeMVar и putMVar с безопасностью исключений. Эти функции гарантируют атомарность только если все другие потоки выполняют takeMVar перед putMVar; в противном случае они могут заблокироваться.
Справедливость
Ни один поток не может быть заблокирован неопределённо долго на MVar, если другой поток не удерживает это MVar неопределённо долго. Одно из обычных решений для обеспечения этой гарантии справедливости заключается в том, что потоки, заблокированные на MVar, обслуживаются по принципу «первый пришёл — первый обслужен» (так делает GHC), но это не гарантируется в семантике.
Особенности
Как и многие другие структуры данных Haskell, MVar являются ленивыми. Это означает, что если вы поместите дорогостоящую невычисленную ленивую вычислительную структуру (thunk) в MVar, она будет вычислена потоком, который её потребляет, а не потоком, который её создал. Убедитесь, что вы evaluate значения, которые нужно поместить в MVar, в соответствующую нормальную форму, или воспользуйтесь строгой MVar из пакета strict-concurrency.
Порядок
MVar операции всегда наблюдаются в порядке их записи в программе, независимо от модели памяти базовой машины. Это в отличие от IORef операций, которые в некоторых случаях могут показаться другому потоку вне порядка.
Пример
Рассмотрим следующую конкурирующую структуру данных — канал с пропуском. Это канал для периодического источника информации с высокой пропускной способностью (например, событий движения мыши.) Запись в канал никогда не блокирует, а чтение из канала возвращает только последнее значение или блокирует, если нет новых значений. Поддержка нескольких читателей с помощью операции dupSkipChan.
Канал с пропуском — это пара MVar. Первый MVar содержит текущее значение и список семафоров, которые необходимо уведомить при изменении. Второй MVar — это семафор для этого конкретного читателя: он заполнен, если в канале есть значение, которое этот читатель ещё не прочитал, и пуст в противном случае.
data SkipChan a = SkipChan (MVar (a, [MVar ()])) (MVar ())
newSkipChan :: IO (SkipChan a)
newSkipChan = do
sem <- newEmptyMVar
main <- newMVar (undefined, [sem])
return (SkipChan main sem)
putSkipChan :: SkipChan a -> a -> IO ()
putSkipChan (SkipChan main _) v = do
(_, sems) <- takeMVar main
putMVar main (v, [])
mapM_ (\sem -> putMVar sem ()) sems
getSkipChan :: SkipChan a -> IO a
getSkipChan (SkipChan main sem) = do
takeMVar sem
(v, sems) <- takeMVar main
putMVar main (v, sem : sems)
return v
dupSkipChan :: SkipChan a -> IO (SkipChan a)
dupSkipChan (SkipChan main _) = do
sem <- newEmptyMVar
(v, sems) <- takeMVar main
putMVar main (v, sem : sems)
return (SkipChan main sem)
Этот пример был адаптирован из оригинальной статьи Concurrent Haskell. Для получения дополнительных примеров использования MVar для создания примитивов синхронизации высокого уровня см. Chan и QSem.
MVars
MVar (произносится "эм-вар") — это синхронизирующая переменная, используемая для связи между конкурирующими потоками. Можно представить её как ящик, который может быть пустым или заполненным.
Примеры использования
newEmptyMVar :: IO (MVar a) Source
Создать MVar, который изначально пуст.
newMVar :: a -> IO (MVar a) Source
Создать MVar, который содержит предоставленное значение.
takeMVar :: MVar a -> IO a Source
Возвращает содержимое MVar. Если MVar в настоящее время пуст, takeMVar будет ожидать, пока он не заполнится. После takeMVar, MVar остаётся пустым.
Есть ещё две важные особенности takeMVar:
-
takeMVar— это однократное пробуждение. То есть, если несколько потоков заблокированы вtakeMVar, иMVarзаполняется, только один поток будет разбужен. Система выполнения гарантирует, что разбуженный поток завершит свою операциюtakeMVar. - Когда несколько потоков заблокированы на
MVar, они разбуживаются в порядке очереди «первый пришёл — первый обслужен». Это полезно для обеспечения справедливости абстракций, созданных с использованиемMVar.
putMVar :: MVar a -> a -> IO () Source
Поместить значение в MVar. Если MVar в настоящее время заполнен, putMVar будет ожидать, пока он не станет пустым.
Есть ещё две важные особенности putMVar:
-
putMVar— это однократное пробуждение. То есть, если несколько потоков заблокированы вputMVar, иMVarстановится пустым, только один поток будет разбужен. Система выполнения гарантирует, что разбуженный поток завершит свою операциюputMVar. - Когда несколько потоков заблокированы на
MVar, они разбуживаются в порядке очереди «первый пришёл — первый обслужен». Это полезно для обеспечения справедливости абстракций, созданных с использованиемMVar.
readMVar :: MVar a -> IO a Source
Атомарно прочитать содержимое MVar. Если MVar в настоящее время пуст, readMVar будет ожидать, пока он не заполнится. readMVar гарантированно получит следующее putMVar.
readMVar — это многократное пробуждение, поэтому когда несколько читателей заблокированы на MVar, все они разбуживаются одновременно. Система выполнения гарантирует, что все разбуженные потоки завершат свою операцию readMVar.
Примечание по совместимости: До версии base 4.7, readMVar был комбинацией takeMVar и putMVar. Это означало, что при наличии других потоков, пытающихся putMVar, readMVar мог заблокироваться. Кроме того, readMVar не получал бы следующий putMVar, если уже был заблокирован ожидающий поток на takeMVar. Старое поведение можно восстановить, реализовав `readMVar` следующим образом:
readMVar :: MVar a -> IO a
readMVar m =
mask_ $ do
a <- takeMVar m
putMVar m a
return a
swapMVar :: MVar a -> a -> IO a Источник
Извлечь значение из MVar, поместить новое значение в MVar и вернуть извлечённое значение. Эта функция атомарна только если нет других производителей для этого MVar. Другими словами, она не может гарантировать, что к моменту, когда swapMVar получит возможность записи в MVar, значение MVar не будет изменено операцией записи из другого потока.
tryTakeMVar :: MVar a -> IO (Maybe a) Источник
Неблокирующая версия takeMVar. Функция tryTakeMVar возвращается немедленно, с Nothing, если MVar была пустой, или с Just a, если MVar была полной с содержимым a. После tryTakeMVar, MVar остаётся пустой.
tryPutMVar :: MVar a -> a -> IO Bool Источник
Неблокирующая версия putMVar. Функция tryPutMVar пытается поместить значение a в MVar, возвращая True при успехе или False в противном случае.
isEmptyMVar :: MVar a -> IO Bool Источник
Проверить, пуст ли данный MVar.
Обратите внимание, что возвращаемое булево значение — это просто мгновенный снимок состояния MVar. К моменту, когда вы среагируете на его результат, MVar может быть заполнен (или опустошен) — будьте очень осторожны при использовании этой операции. Используйте tryTakeMVar вместо этого, если это возможно.
withMVar :: MVar a -> (a -> IO b) -> IO b Источник
withMVar — это защищённая от исключений обёртка для работы со содержимым MVar. Эта операция защищена от исключений: она заменит исходное содержимое MVar в случае возникновения исключения (см. Control.Exception). Однако она атомарна только если нет других производителей для этого MVar. Другими словами, она не может гарантировать, что к моменту, когда withMVar получит возможность записи в MVar, значение MVar не будет изменено операцией записи из другого потока.
withMVarMasked :: MVar a -> (a -> IO b) -> IO b Источник
Как withMVar, но действие IO во втором аргументе выполняется с замаскированными асинхронными исключениями.
С: base-4.7.0.0
modifyMVar_ :: MVar a -> (a -> IO a) -> IO () Источник
Защищённая от исключений обёртка для изменения содержимого MVar. Как и withMVar, modifyMVar заменит исходное содержимое MVar в случае возникновения исключения во время операции. Эта функция атомарна только если нет других производителей для этого MVar. Другими словами, она не может гарантировать, что к моменту, когда modifyMVar_ получит возможность записи в MVar, значение MVar не будет изменено операцией записи из другого потока.
modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b Источник
Небольшая вариация modifyMVar_ которая позволяет вернуть значение (b) в дополнение к изменённому значению MVar.
modifyMVarMasked_ :: MVar a -> (a -> IO a) -> IO () Источник
Как modifyMVar_, но действие IO во втором аргументе выполняется с замаскированными асинхронными исключениями.
С: base-4.6.0.0
modifyMVarMasked :: MVar a -> (a -> IO (a, b)) -> IO b Источник
Как modifyMVar, но действие IO во втором аргументе выполняется с замаскированными асинхронными исключениями.
С: base-4.6.0.0
tryReadMVar :: MVar a -> IO (Maybe a) Источник
Неблокирующая версия readMVar. Функция tryReadMVar возвращается немедленно, с Nothing, если MVar была пустой, или с Just a, если MVar была полной с содержимым a.
С: base-4.7.0.0
mkWeakMVar :: MVar a -> IO () -> IO (Weak (MVar a)) Источник
Создать указатель Weak на MVar, используя второй аргумент как финализатор для выполнения, когда MVar будет собран сборщиком мусора.
С: base-4.6.0.0
addMVarFinalizer :: MVar a -> IO () -> IO () Источник
Устарело: используйте mkWeakMVar вместо этого
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/9.12.1/docs/libraries/base-4.21.0.0-8e62/Control-Concurrent-MVar.html