Data.IORef
| Авторские права | (c) Университет Глазго 2001 |
|---|---|
| Лицензия | BSD (см. файл libraries/base/LICENSE) |
| Поддержка | libraries@haskell.org |
| Стабильность | стабильная |
| Переносимость | переносимая |
| Safe Haskell | Безопасная |
| Язык | Haskell2010 |
Содержание
Описание
Изменяемые ссылки в монаде IO.
IORefs
Изменяемая переменная в монаде IO.
>>> import GHC.Internal.Data.IORef >>> r <- newIORef 0 >>> readIORef r 0 >>> writeIORef r 1 >>> readIORef r 1 >>> atomicWriteIORef r 2 >>> readIORef r 2 >>> modifyIORef' r (+ 1) >>> readIORef r 3 >>> atomicModifyIORef' r (\a -> (a + 1, ())) >>> readIORef r 4
Примеры использования
newIORef :: a -> IO (IORef a) Источник
Создание новой IORef.
readIORef :: IORef a -> IO a Источник
Чтение значения из IORef.
Обратите внимание, что процессор, выполняющий поток, может переупорядочить чтения или записи в независимые области памяти. Подробнее см. Data.IORef.
writeIORef :: IORef a -> a -> IO () Источник
Запись нового значения в IORef.
Эта функция не создаёт барьер памяти и может быть переупорядочена с другими независимыми чтениями и записями в рамках потока, что может привести к проблемам при многопоточной обработке. В таких случаях рассмотрите использование atomicWriteIORef вместо этого. Подробнее см. Data.IORef.
modifyIORef :: IORef a -> (a -> a) -> IO () Источник
Изменение содержимого IORef, комбинируя readIORef и writeIORef. Это не атомарное обновление, рассмотрите использование atomicModifyIORef при работе в многопоточной среде.
Обратите внимание, что modifyIORef не применяет функцию строго. Это означает, что если программа вызывает modifyIORef много раз, но редко использует значение, в памяти будут накапливаться задержки, что приведёт к утечке памяти. Это распространённая ошибка при использовании IORef в качестве счётчика. Например, следующее, скорее всего, приведёт к переполнению стека:
ref <- newIORef 0 replicateM_ 1000000 $ modifyIORef ref (+1) readIORef ref >>= print
Чтобы избежать этой проблемы, используйте modifyIORef' вместо этого.
modifyIORef' :: IORef a -> (a -> a) -> IO () Источник
Строгая версия modifyIORef. Это не атомарное обновление, рассмотрите использование atomicModifyIORef' при работе в многопоточной среде.
С версии: base-4.6.0.0
atomicModifyIORef :: IORef a -> (a -> (a, b)) -> IO b Источник
Атомарно изменяет содержимое IORef.
Эта функция полезна для безопасного использования IORef в многопоточной программе. Если у вас только один IORef, то использование atomicModifyIORef для доступа и изменения его предотвратит гонки.
Расширение атомарности до нескольких IORef проблематично, поэтому рекомендуется, если вам нужно сделать что-то более сложное, использование MVar вместо этого — хороший вариант.
Концептуально,
atomicModifyIORef ref f = do -- Begin atomic block old <- readIORef ref let r = f old new = fst r writeIORef ref new -- End atomic block case r of (_new, res) -> pure res
Действия в разделе, помеченном как «атомарный блок», не подвергаются вмешательству других потоков. В частности, значение в IORef не может измениться между вызовами readIORef и writeIORef.
Функция, предоставленная пользователем, применяется к значению, хранящемуся в IORef, давая новое значение для хранения в IORef и значение для возврата. После того, как новое значение (лениво) будет сохранено в IORef, atomicModifyIORef принудительно формирует пару результатов, но не принуждает ни один из компонентов результата. Для принудительного формирования обоих компонентов используйте atomicModifyIORef'.
Обратите внимание, что
atomicModifyIORef ref (_ -> undefined)
вызовет исключение в вызывающем потоке, но также установит значение в IORef, где оно может быть прочитано другими потоками.
Эта функция накладывает барьер памяти, предотвращая переупорядочение вокруг «атомарного блока»; см. Data.IORef для получения подробностей.
atomicModifyIORef' :: IORef a -> (a -> (a, b)) -> IO b Источник
Строгая версия atomicModifyIORef. Эта версия принуждает к формированию значения, хранящегося в IORef, и возвращаемого значения.
Концептуально,
atomicModifyIORef' ref f = do -- Begin atomic block old <- readIORef ref let r = f old new = fst r writeIORef ref new -- End atomic block case r of (!_new, !res) -> pure res
Действия в «атомарном блоке» не подвергаются влиянию других потоков. В частности, значение в IORef не может измениться между вызовами readIORef и writeIORef.
Новое значение устанавливается в IORef до принудительного формирования любого значения. Так
atomicModifyIORef' ref (x -> (x+1, undefined))
увеличит IORef и затем вызовет исключение в вызывающем потоке.
atomicModifyIORef' ref (x -> (undefined, x))
и
atomicModifyIORef' ref (_ -> undefined)
каждый из них вызовет исключение в вызывающем потоке, но также установит значение в IORef, где оно может быть прочитано другими потоками.
Эта функция накладывает барьер памяти, предотвращая переупорядочение вокруг «атомарного блока»; см. Data.IORef для получения подробностей.
С версии: base-4.6.0.0
atomicWriteIORef :: IORef a -> a -> IO () Источник
Вариант writeIORef. Префикс «атомарный» относится к тому факту, что он накладывает барьер переупорядочения, подобно atomicModifyIORef . Такая запись не будет переупорядочена с другими чтениями или записями даже на процессорах со слабой моделью памяти.
С версии: base-4.6.0.0
mkWeakIORef :: IORef a -> IO () -> IO (Weak (IORef a)) Источник
Создать Weak указатель на IORef, используя второй аргумент как финализатор для выполнения, когда IORef будет собрано сборщиком мусора.
Модель памяти
Большинство современных архитектур процессоров (например, x86/64, ARM) имеют модель памяти, которая позволяет потокам переупорядочивать чтения с предыдущими записями в различные места, например, см. справочник по архитектуре x86/64, 8.2.3.4 Загрузки могут быть переупорядочены с предыдущими записями в различные места.
Из-за этого в конкурентной программе операции IORef могут появляться вне порядка для другого потока. В следующем примере:
import GHC.Internal.Data.IORef import GHC.Internal.Control.Monad (unless) import Control.Concurrent (forkIO, threadDelay) maybePrint :: IORef Bool -> IORef Bool -> IO () maybePrint myRef yourRef = do writeIORef myRef True yourVal <- readIORef yourRef unless yourVal $ putStrLn "critical section" main :: IO () main = do r1 <- newIORef False r2 <- newIORef False forkIO $ maybePrint r1 r2 forkIO $ maybePrint r2 r1 threadDelay 1000000
Возможна ситуация, когда строка "critical section" будет напечатана дважды, даже если нет переплетения операций двух потоков, позволяющего такой результат. Модель памяти x86/64 допускает, что readIORef произойдет до более раннего writeIORef.
Модель порядка памяти ARM обычно ещё слабее, чем x86/64, разрешая любое переупорядочение чтений и записей, пока они независимы с точки зрения текущего потока.
Реализация должна гарантировать, что переупорядочение операций с памятью не может привести к некорректной работе типа-правильного кода. В частности, при проверке значения, считанного из IORef, записи в память, создавшие это значение, должны произойти с точки зрения текущего потока.
atomicWriteIORef, atomicModifyIORef и atomicModifyIORef' действуют как барьер для переупорядочения. Несколько вызовов этих функций происходят в строгом порядке программы, никогда не выполняясь раньше любых более ранних (в порядке программы) IORef операций или после любых последующих IORef операций.
© 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/Data-IORef.html