Control.Concurrent.MVar
| Авторские права | (c) The University of Glasgow 2001 |
|---|---|
| Лицензия | BSD-style (см. файл libraries/base/LICENSE) |
| Сопровождающий | libraries@haskell.org |
| Стабильность | стабильный |
| Переносимость | непереносимый (параллелизм) |
| Safe Haskell | Safe |
| Язык | Haskell2010 |
Содержание
Описание
MVar t — это изменяемая область памяти, которая либо пуста, либо содержит значение типа t. У неё есть две основные операции: putMVar, которая заполняет MVar, если она пуста, и блокируется в противном случае, и takeMVar, которая опустошает MVar, если она заполнена, и блокируется в противном случае. Их можно использовать различными способами:
- В качестве синхронизированных изменяемых переменных,
- В качестве каналов, где
takeMVarиputMVarслужат для получения и отправки данных, и - В качестве двоичного семафора
MVar (), гдеtakeMVarиputMVarслужат для ожидания и подачи сигнала.
Они были представлены в статье "Concurrent Haskell" Саймона Пейтона Джонса, Эндрю Гордона и Сигбьёрна Финне, хотя с тех пор некоторые детали их реализации изменились (в частности, раньше попытка записать значение в заполненную MVar приводила к ошибке, а теперь просто блокируется).
Применимость
MVars предоставляют больше возможностей, чем IORefs, но меньше, чем STM. Они подходят для создания примитивов синхронизации и простого обмена данными между потоками; однако они очень просты и подвержены гонкам данных, взаимным блокировкам и необработанным исключениям. Не используйте их, если вам нужно выполнять более крупные атомарные операции, например чтение нескольких переменных: вместо этого используйте STM.
В частности, «более сложные» функции этого модуля (swapMVar, withMVar, modifyMVar_ и modifyMVar) — это просто композиция takeMVar и следующей за ней putMVar, обеспечивающая безопасность при возникновении исключений. Они гарантируют атомарность только в том случае, если все остальные потоки также выполняют takeMVar перед putMVar; в противном случае они могут заблокироваться.
Справедливость
Ни один поток не может бесконечно долго блокироваться на MVar, если только другой поток не удерживает эту MVar бесконечно долго. Обычно гарантия справедливости реализуется так: потоки, заблокированные на MVar, обслуживаются в порядке FIFO (так работает GHC), однако семантика этого не гарантирует.
Подводные камни
Как и многие другие структуры данных Haskell, MVars ленивы. Это означает, что если поместить дорогостоящий невычисленный thunk в MVar, его вычислит поток, который его извлекает, а не поток, который его создал. Обязательно приведите evaluate значения, помещаемые в MVar, к соответствующей нормальной форме или используйте строгую MVar из пакета strict-concurrency.
Порядок выполнения
Операции MVar всегда наблюдаются в том порядке, в котором они записаны в программе, независимо от модели памяти базовой машины. Это отличается от операций IORef, которые в некоторых случаях могут казаться другому потоку выполненными не по порядку.
Пример
Рассмотрим следующую структуру данных для параллельной работы — канал с пропуском. Это канал для периодически поступающих данных с высокой пропускной способностью (например, событий перемещения мыши). Запись в канал никогда не блокируется, а чтение возвращает только самое последнее значение или блокируется, если новых значений нет. Поддерживается несколько читателей благодаря операции dupSkipChan.
Канал с пропуском — это пара MVars. Первый 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. Другие примеры использования MVars для создания примитивов синхронизации более высокого уровня см. в 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, они пробуждаются в порядке FIFO. Это полезно для обеспечения справедливости абстракций, построенных с использованиемMVars.
putMVar :: MVar a -> a -> IO () Source
Помещает значение в MVar. Если MVar сейчас заполнена, putMVar будет ждать, пока она не опустеет.
У putMVar есть ещё два важных свойства:
-
putMVarпробуждает только один поток. То есть, если несколько потоков заблокированы вputMVarиMVarопустошается, будет разбужен только один поток. Среда выполнения гарантирует, что разбуженный поток завершит операциюputMVar. - Если несколько потоков заблокированы на
MVar, они пробуждаются в порядке FIFO. Это полезно для обеспечения справедливости абстракций, построенных с использованиемMVars.
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 Source
Извлекает значение из MVar, помещает новое значение в MVar и возвращает извлечённое значение. Эта функция атомарна только при отсутствии других производителей для этой MVar. Иными словами, она не гарантирует, что к моменту, когда swapMVar получит возможность записать значение в MVar, значение MVar не будет изменено операцией записи из другого потока.
tryTakeMVar :: MVar a -> IO (Maybe a) Source
Неблокирующая версия takeMVar. Функция tryTakeMVar возвращает результат немедленно: Nothing, если MVar пуста, или Just a, если MVar заполнена содержимым a. После выполнения tryTakeMVar MVar остаётся пустой.
tryPutMVar :: MVar a -> a -> IO Bool Source
Неблокирующая версия putMVar. Функция tryPutMVar пытается поместить значение a в MVar и возвращает True в случае успеха или False в противном случае.
isEmptyMVar :: MVar a -> IO Bool Source
Проверяет, пуста ли заданная MVar.
Обратите внимание, что возвращаемое логическое значение — это лишь снимок состояния MVar. К тому моменту, когда вы отреагируете на результат, MVar может быть заполнена (или опустошена), поэтому используйте эту операцию с крайней осторожностью. По возможности вместо неё используйте tryTakeMVar.
withMVar :: MVar a -> (a -> IO b) -> IO b Source
withMVar — это безопасная при возникновении исключений обёртка для работы с содержимым MVar. Эта операция безопасна при возникновении исключений: если возникает исключение, она восстанавливает исходное содержимое MVar (см. Control.Exception). Однако она атомарна только при отсутствии других производителей для этой MVar. Иными словами, она не гарантирует, что к моменту, когда withMVar получит возможность записать значение в MVar, значение MVar не будет изменено операцией записи из другого потока.
withMVarMasked :: MVar a -> (a -> IO b) -> IO b Source
Подобно withMVar, но действие IO во втором аргументе выполняется с маскировкой асинхронных исключений.
Начиная с: base-4.7.0.0
modifyMVar_ :: MVar a -> (a -> IO a) -> IO () Source
Безопасная при возникновении исключений обёртка для изменения содержимого MVar. Как и withMVar, modifyMVar восстанавливает исходное содержимое MVar, если во время операции возникает исключение. Эта функция атомарна только при отсутствии других производителей для этой MVar. Иными словами, она не гарантирует, что к моменту, когда modifyMVar_ получит возможность записать значение в MVar, значение MVar не будет изменено операцией записи из другого потока.
modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b Source
Небольшое изменение modifyMVar_, позволяющее возвращать значение (b) вместе с изменённым значением MVar.
modifyMVarMasked_ :: MVar a -> (a -> IO a) -> IO () Source
Подобно modifyMVar_, но действие IO во втором аргументе выполняется с маскировкой асинхронных исключений.
Начиная с: base-4.6.0.0
modifyMVarMasked :: MVar a -> (a -> IO (a, b)) -> IO b Source
Подобно modifyMVar, но действие IO во втором аргументе выполняется с маскировкой асинхронных исключений.
Начиная с: base-4.6.0.0
tryReadMVar :: MVar a -> IO (Maybe a) Source
Неблокирующая версия readMVar. Функция tryReadMVar возвращает результат немедленно: Nothing, если MVar пуста, или Just a, если MVar заполнена содержимым a.
Начиная с: base-4.7.0.0
mkWeakMVar :: MVar a -> IO () -> IO (Weak (MVar a)) Source
Создаёт слабый указатель Weak на MVar, используя второй аргумент в качестве финализатора, который будет запущен при сборке мусора MVar
Начиная с: base-4.6.0.0
addMVarFinalizer :: MVar a -> IO () -> IO () Source
Устарело: используйте вместо этого mkWeakMVar
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/9.14.1/docs/libraries/base-4.22.0.0-66f8/Control-Concurrent-MVar.html