System.Mem.Weak
| Авторские права | (c) Университет Глазго 2001 |
|---|---|
| Лицензия | BSD-стиль (см. файл libraries/base/LICENSE) |
| Поддержка | libraries@haskell.org |
| Стабильность | экспериментальная |
| Переносимость | непереносимая |
| Safe Haskell | Надёжная |
| Язык | Haskell2010 |
Описание
В общих чертах, слабая ссылка — это ссылка на объект, за которой сборщик мусора не следит — то есть существование слабой ссылки на объект не влияет на срок жизни этого объекта. Слабую ссылку можно разыменовать, чтобы узнать, жив ли объект, на который она ссылается, и, если жив, вернуть сам объект.
Слабые ссылки особенно полезны для кэшей и таблиц мемоизации. Чтобы создать таблицу мемоизации, вы создаёте структуру данных, сопоставляющую аргумент функции (ключ) с её результатом (значение). При применении функции к новому аргументу вы сначала проверяете, есть ли пара ключ/значение в таблице мемоизации. Важно, что сама таблица мемоизации не должна поддерживать ключ и значение живыми. Поэтому таблица должна содержать слабую ссылку на ключ, а не обычную ссылку. Ссылка на значение должна быть сильной, потому что единственная ссылка на значение может быть именно от таблицы мемоизации.
Кажется, что таблица мемоизации будет хранить все свои значения вечно. Один способ решения этой проблемы — периодически очищать таблицу, удаляя записи, ключи которых умерли.
Слабые ссылки в этой библиотеке поддерживают другой подход, называемый финализацией. Когда ключ, на который указывает слабая ссылка, умирает, диспетчер памяти организует выполнение указанного программистом финализатора. В случае таблиц мемоизации, например, финализатор может удалить пару ключ/значение из таблицы мемоизации.
Ещё одна проблема с таблицей мемоизации заключается в том, что значение пары ключ/значение может содержать ссылку на ключ. Таким образом, таблица мемоизации сохраняет значение живым, что сохраняет ключ живым, даже если больше нет других ссылок на ключ, и оба должны умереть. Слабые ссылки в этой библиотеке предлагают небольшое обобщение базовой идеи слабой ссылки, в которой каждая слабая ссылка фактически содержит и ключ, и значение.
Тип Weak
Объект слабой ссылки с ключом и значением. Значение имеет тип v.
Слабая ссылка выражает взаимосвязь между двумя объектами, ключом и значением: если сборщик мусора считает ключ живым, то значение также считается живым. Ссылка со значения на ключ не сохраняет ключ живым.
У слабой ссылки также может быть финализатор типа IO (); если он есть, то финализатор будет выполнен не более одного раза, в момент, когда ключ станет недоступным для программы («мёртвым»). Диспетчер памяти пытается выполнить финализатор(ы) объекта вскоре после смерти объекта, но своевременность не гарантируется.
Не гарантируется, что финализатор будет в конечном итоге выполнен, и никаких попыток выполнить незавершенные финализаторы при выходе из программы не предпринимается. Поэтому не стоит полагаться на финализаторы для очистки ресурсов — следует использовать другие методы (например, обработчики исключений), возможно, в дополнение к финализаторам.
Ссылки из финализатора на ключ обрабатываются так же, как ссылки со значения на ключ: они не сохраняют ключ живым. Таким образом, финализатор может оживить ключ, возможно, сохранив его в той же структуре данных.
Финализатор и взаимосвязь между ключом и значением существуют независимо от того, сохраняет ли программа ссылку на объект Weak или нет.
Может быть несколько слабых ссылок с одинаковым ключом. В этом случае финализаторы для каждой из этих слабых ссылок будут выполнены в произвольном порядке или, возможно, одновременно, когда ключ умрёт. Если программист указывает финализатор, предполагающий, что он имеет единственную ссылку на объект (например, файл, который он хочет закрыть), то программист должен убедиться, что существует только один такой финализатор.
Если нет других потоков для выполнения, система выполнения проверит наличие выполняемых финализаторов перед объявлением системы заблокированной.
ПРЕДУПРЕЖДЕНИЕ: слабые ссылки на обычные не-примитивные типы Haskell особенно хрупки, поскольку компилятор свободен оптимизировать или дублировать базовую структуру данных. Поэтому попытка установить финализатор на обычный тип Haskell может привести к тому, что финализатор будет выполнен раньше, чем ожидалось. Это не проблема для кэшей и таблиц мемоизации, где ранний финализатор безвреден.
Финализаторы могут использоваться надёжно для типов, которые создаются явно и имеют идентичность, такие как IORef и MVar. Однако для установки финализатора на один из этих типов необходимо использовать специальную операцию, предоставленную для этого типа, например, mkWeakIORef и addMVarFinalizer соответственно (неравномерность случайная). Эти операции прикрепляют финализатор к примитивному объекту внутри ящика (например, MutVar# в случае IORef), потому что прикрепление финализатора к самому ящику терпит неудачу, когда внешний ящик оптимизируется компилятором.
Общий интерфейс
Аргументы
| :: k | ключ |
| -> v | значение |
| -> Maybe (IO ()) | финализатор |
| -> IO (Weak v) | возвращает: объект слабой ссылки |
Создаёт слабую ссылку на k, со значением v и финализатором.
Это наиболее общий интерфейс для создания слабой ссылки.
deRefWeak :: Weak v -> IO (Maybe v) Источник
Разыменование слабой ссылки. Если ключ всё ещё жив, то возвращается Just v (где v — значение в слабой ссылке), в противном случае возвращается Nothing.
Значение возвращаемое deRefWeak зависит от времени запуска сборщика мусора, поэтому оно находится в монаде IO.
finalize :: Weak v -> IO () Источник
Вызывает выполнение финализатора, связанного со слабой ссылкой, немедленно.
Специализированные версии
mkWeakPtr :: k -> Maybe (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 -> Maybe (IO ()) -> IO (Weak (k, v)) Источник
Специализированная версия mkWeak, где значение фактически является парой ключа и значения, переданных mkWeakPair:
mkWeakPair key val finalizer = mkWeak key (key,val) finalizer
Преимущество этого заключается в том, что ключ можно получить с помощью deRefWeak в дополнение к значению.
Точное семантическое описание
Вышеуказанное неформальное описание подходит для простых ситуаций, но дела могут осложниться. В частности, необходимо чётко указать, когда ключ умирает, чтобы любые слабые ссылки, которые на него ссылаются, могли быть финализированы. Предположим, например, что значение одной слабой ссылки ссылается на ключ другой... сохраняет ли это ключ живым?
Поведение простое:
- Если слабая ссылка (объект) ссылается на недоступный ключ, она может быть финализирована.
- Финализация означает (а) организовать, чтобы последующие вызовы
deRefWeakвозвращалиNothing; и (б) выполнить финализатор.
Это поведение зависит от того, что означает доступность ключа. Неформально, что-то доступно, если к нему можно добраться, пройдя по обычным ссылкам от корневого набора, но не следуя слабым ссылкам. Мы определяем доступность более точно следующим образом.
Объект кучи является доступным, если:
- Он является членом корневого набора.
- Он напрямую указывается достижимым объектом, отличным от объекта слабого указателя.
- Это объект слабого указателя, ключ которого достижим.
- Это значение или финализатор объекта слабого указателя, ключ которого достижим.
Примечания по реализации
Финализатор не всегда вызывается после того, как объект его слабого указателя становится недостижимым. Существуют две ситуации, которые могут привести к этому:
- Если объект становится недостижимым непосредственно перед завершением программы, GC может не выполняться. Финализаторы выполняются во время GC, поэтому финализаторы, связанные с объектом, не выполняются, если GC не происходит.
- Если финализатор вызывает исключение, последующие финализаторы, которые были помещены в очередь для выполнения после него, не выполняются. Это поведение может измениться в будущей версии. См. вопрос 13167 на портале отслеживания проблем. Не рекомендуется писать финализатор, который вызывает исключения.
Помимо этих двух оговорок, пользователи всегда могут ожидать, что финализатор будет запущен после того, как объект его слабого указателя станет недостижимым. Однако вторая оговорка означает, что пользователи должны доверять, что все их транзитивные зависимости не вызывают исключений в финализаторах, поскольку любые финализаторы могут оказаться в очереди вместе.
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/8.10.2/docs/html/libraries/base-4.14.1.0/System-Mem-Weak.html