Data.IORef
| Авторские права | (c) The University of Glasgow 2001 |
|---|---|
| Лицензия | в стиле BSD (см. файл libraries/base/LICENSE) |
| Сопровождающий | libraries@haskell.org |
| Стабильность | стабильный |
| Переносимость | переносимый |
| Безопасный Haskell | Safe |
| Язык | Haskell2010 |
Содержание
Описание
Изменяемые ссылки в монаде IO.
IORefs
data IORef a Исходный код
Изменяемая переменная в монаде 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
Экземпляры
| Eq (IORef a) Исходный код |
Равенство указателей. С версии: base-4.0.0.0 |
Определён в GHC.Internal.IORef Методы(==) :: IORef a -> IORef a -> Bool Исходный код (/=) :: IORef a -> IORef a -> Bool Исходный код | |
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. Префикс «atomic» указывает на то, что функция устанавливает барьер переупорядочивания, аналогичный 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.14.1/docs/libraries/base-4.22.0.0-66f8/Data-IORef.html