System.Mem.Weak
| Copyright | (c) The University of Glasgow 2001 |
|---|---|
| License | BSD-style (see the file libraries/base/LICENSE) |
| Maintainer | libraries@haskell.org |
| Stability | stable |
| Portability | non-portable |
| Safe Haskell | Trustworthy |
| Language | Haskell2010 |
Описание
В общем случае, слабая ссылка — это ссылка на объект, не отслеживаемая сборщиком мусора — то есть, существование слабой ссылки на объект не влияет на срок жизни этого объекта. Слабую ссылку можно разыменовать, чтобы узнать, жив ли объект, на который она ссылается, и, если жив, вернуть сам объект.
Слабые ссылки особенно полезны для кэшей и таблиц мемоизации. Для создания таблицы мемоизации необходимо создать структуру данных, сопоставляющую аргумент функции (ключ) с ее результатом (значение). При применении функции к новому аргументу сначала проверяется, есть ли пара ключ/значение в таблице мемоизации. Суть в том, что сама таблица мемоизации не должна хранить ключ и значение живыми. Поэтому таблица должна содержать слабую ссылку на ключ, а не обычную. Ссылка на значение не должна быть слабой, потому что единственной ссылкой на значение может быть именно таблица мемоизации.
Таким образом, кажется, что таблица мемоизации будет хранить все свои значения вечно. Один из способов решения этой проблемы — периодически очищать таблицу, удаляя записи, ключи которых умерли.
Слабые ссылки в этом модуле поддерживают другой подход, называемый финализацией. Когда ключ, на который указывает слабая ссылка, умирает, менеджер памяти организует выполнение указанного программистом финализатора. Например, в случае таблиц мемоизации финализатор может удалить пару ключ/значение из таблицы мемоизации.
Другая сложность с таблицей мемоизации заключается в том, что значение пары ключ/значение может само содержать указатель на ключ. Таким образом, таблица мемоизации сохраняет значение живым, что сохраняет ключ живым, даже если больше нет других ссылок на ключ, и оба должны умереть. Слабые ссылки в этом модуле обеспечивают небольшое обобщение основной идеи слабой ссылки, при которой каждая слабая ссылка фактически содержит как ключ, так и значение.
Подробное обсуждение слабых ссылок см. в статье «Расширение менеджера памяти: слабые ссылки и стабильные имена в Haskell» Саймона Пейтона Джонса, Саймона Марлоу и Конала Эллиота. Реализация таблицы мемоизации со слабыми ссылками доступна в пакете stable-memo.
Тип Weak
Объект слабой ссылки с ключом и значением. Значение имеет тип v.
Слабая ссылка выражает связь между двумя объектами, ключом и значением: если сборщик мусора считает ключ живым, то значение также считается живым. Ссылка из значения на ключ не сохраняет ключ живым.
Слабая ссылка также может иметь финализатор типа IO (); если он есть, то финализатор будет выполнен не более одного раза, после того как ключ станет недоступным для программы ("мертвым"). Менеджер памяти пытается выполнить финализаторы объекта вскоре после смерти объекта, но своевременность не гарантируется.
Не гарантируется, что финализатор в конечном итоге будет выполнен, и не предпринимается попыток выполнить незавершенные финализаторы при выходе программы. Поэтому на финализаторах не следует полагаться для освобождения ресурсов — следует использовать другие методы (например, обработчики исключений), возможно, дополнительно к финализаторам.
Ссылки из финализатора на ключ обрабатываются так же, как и ссылки из значения на ключ: они не сохраняют ключ живым. Таким образом, финализатор может оживить ключ, возможно, сохранив его в той же структуре данных.
Финализатор и связь между ключом и значением существуют независимо от того, сохраняет ли программа ссылку на объект Weak или нет.
Может быть несколько слабых ссылок с одним и тем же ключом. В этом случае финализаторы каждой из этих слабых ссылок будут выполняться в произвольном порядке или, возможно, одновременно, когда ключ умирает. Если программист указывает финализатор, предполагающий, что у него есть единственная ссылка на объект (например, файл, который он хочет закрыть), то программист должен убедиться, что такой финализатор только один.
Если нет других потоков для выполнения, система выполнения проверит наличие запустимых финализаторов, прежде чем объявить систему заблокированной.
ПРЕДУПРЕЖДЕНИЕ: слабые ссылки на обычные непримитивные типы Haskell особенно хрупкие, поскольку компилятор свободен оптимизировать или дублировать базовую структуру данных. Поэтому попытка назначить финализатор на обычный тип Haskell может привести к выполнению финализатора раньше, чем ожидалось. Это не проблема для кэшей и таблиц мемоизации, где раннее выполнение финализатора безвредно.
Финализаторы могут надежно использоваться для типов, которые создаются явно и имеют идентичность, такие как IORef, MVar, и TVar. Однако для назначения финализатора на один из этих типов следует использовать специальную операцию, предоставленную для этого типа, например, mkWeakIORef, mkWeakMVar и mkWeakTVar соответственно. Эти операции прикрепляют финализатор к примитивному объекту внутри контейнера (например, MutVar# в случае IORef), потому что прикрепление финализатора к самому контейнеру завершается неудачей, когда внешний контейнер оптимизируется компилятором.
Общий интерфейс
Аргументы
| :: k | ключ |
| -> v | значение |
| -> Возможно (IO ()) | финализатор |
| -> IO (Weak v) | возвращает: объект слабой ссылки |
Создает слабую ссылку на k, со значением v и финализатором.
Это самый общий интерфейс для создания слабой ссылки.
deRefWeak :: Weak v -> IO (Возможно v) Источник
Разыменовывает слабую ссылку. Если ключ еще жив, то возвращается Just v (где v — значение в слабой ссылке), в противном случае возвращается Nothing.
Возвращаемое значение deRefWeak зависит от того, когда работает сборщик мусора, поэтому оно находится в монаде IO.
finalize :: Weak v -> IO () Источник
Вызывает немедленное выполнение финализатора, связанного со слабой ссылкой.
Специализированные версии
mkWeakPtr :: k -> Возможно (IO ()) -> IO (Weak k) Источник
Специализированная версия mkWeak, где ключ и значение — один и тот же объект:
mkWeakPtr key finalizer = mkWeak key key finalizer
addFinalizer :: key -> IO () -> IO () Источник
Специализированная версия mkWeakPtr, где возвращаемый объект Weak просто отбрасывается (однако финализатор будет сохранен сборщиком мусора и все равно будет выполнен, когда ключ станет недоступным).
Примечание: добавление финализатора к ForeignPtr с помощью addFinalizer не сработает; используйте специализированную версию addForeignPtrFinalizer вместо этого. Для обсуждения см. тип Weak.
mkWeakPair :: k -> v -> Возможно (IO ()) -> IO (Weak (k, v)) Источник
Специализированная версия mkWeak, где значение фактически является парой ключа и значения, переданной в mkWeakPair:
mkWeakPair key val finalizer = mkWeak key (key,val) finalizer
Преимущество этого в том, что ключ можно получить с помощью deRefWeak помимо значения.
Обработка исключений
Когда финализатор, вызываемый сборщиком мусора, выбрасывает исключение, GHC вызывает глобальный обработчик, который можно установить с помощью setFinalizerExceptionHandler. Обратите внимание, что любые исключения, сгенерированные этим обработчиком, будут проигнорированы.
setFinalizerExceptionHandler :: (SomeException -> IO ()) -> IO () Source
Установите глобальное действие, вызываемое для сообщения о исключениях, сгенерированных финализаторами слабых указателей пользователю.
Since: base-4.18.0.0
getFinalizerExceptionHandler :: IO (SomeException -> IO ()) Source
Получить глобальное действие, вызываемое для сообщения о исключениях, сгенерированных финализаторами слабых указателей пользователю.
Since: base-4.18.0.0
printToHandleFinalizerExceptionHandler :: Handle -> SomeException -> IO () Source
Обработчик исключений для Handle финализации, который выводит ошибку в указанный Handle, но не перебрасывает её.
Since: base-4.18.0.0
Точное семантическое описание
Вышеприведенное неформальное описание подходит для простых случаев, но может усложняться. В частности, необходимо четко указать, когда ключ умирает, чтобы все слабые указатели, которые ссылаются на него, могли быть завершены. Например, предположим, что значение одного слабого указателя ссылается на ключ другого...это сохраняет ли ключ живым?
Поведение простое:
- Если слабый указатель (объект) ссылается на недостижимый ключ, он может быть завершён.
- Завершение означает (а) организовать, чтобы последующие вызовы
deRefWeakвозвращалиNothing; и (б) выполнить финализатор.
Это поведение зависит от того, что означает, что ключ достижим. Неформально, что-то достижимо, если к нему можно добраться, следуя обычным указателям из набора корней, но не следуя слабым указателям. Мы определяем достижимость более точно следующим образом.
Объект кучи достижим, если:
- Он является членом корневого набора.
- К нему напрямую ссылается достижимый объект, кроме слабого указателя.
- Это слабый указатель, ключ которого достижим.
- Это значение или финализатор слабого указателя, ключ которого достижим.
Примечания по реализации
Финализатор не всегда вызывается после того, как объект его слабого указателя становится недостижимым. Если объект становится недостижимым непосредственно перед завершением программы, то сбор мусора может не выполняться. Финализаторы выполняются во время сбора мусора, поэтому финализаторы, связанные с объектом, не выполняются, если сборка мусора не происходит.
За исключением вышеуказанного замечания, пользователи всегда могут ожидать, что финализатор будет выполнен после того, как объект его слабого указателя станет недостижимым.
Если финализатор выбрасывает исключение, исключение молча перехватывается без уведомления. Подробности см. в коммите проблемы 13167. Не рекомендуется писать финализаторы, которые выбрасывают исключения.
© 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/System-Mem-Weak.html