Почему парадигма деструктора объекта в языках с мусорным сбором повсеместно отсутствует?


27

Ищите понимание решений, связанных с языковым дизайном, собираемым мусором. Возможно, специалист по языку мог бы просветить меня? Я родом из C ++, так что эта область сбивает меня с толку.

Кажется, что почти все современные языки со сборкой мусора с поддержкой объектов OOPy, такие как Ruby, Javascript / ES6 / ES7, Actionscript, Lua и т. Д., Полностью опускают парадигму деструктора / финализации. Python, кажется, единственный с его class __del__()методом. Почему это? Существуют ли функциональные / теоретические ограничения в языках с автоматической сборкой мусора, которые мешают эффективной реализации метода деструктора / финализации на объектах?

Мне крайне не хватает того, что эти языки рассматривают память как единственный ресурс, которым стоит управлять. Как насчет сокетов, файловых дескрипторов, состояний приложений? Без возможности реализовать пользовательскую логику для очистки ресурсов, не связанных с памятью, и состояний при финализации объекта, я должен засорять свое приложение myObject.destroy()вызовами пользовательского стиля, размещать логику очистки вне моего «класса», нарушать попытки инкапсуляции и откладывать мои приложение к утечке ресурсов из-за человеческой ошибки, а не автоматически обрабатывается gc.

Какие решения по проектированию языка приводят к тому, что эти языки не имеют возможности выполнять пользовательскую логику при удалении объектов? Я должен представить, что есть веская причина. Я хотел бы лучше понять технические и теоретические решения, которые привели к тому, что эти языки не поддерживают уничтожение / финализацию объектов.

Обновить:

Возможно, лучший способ сформулировать мой вопрос:

Почему язык имеет встроенную концепцию экземпляров объектов с классом или классоподобными структурами вместе с пользовательскими экземплярами (конструкторами), но при этом полностью исключает функциональность уничтожения / финализации? Языки, которые предлагают автоматическую сборку мусора, являются основными кандидатами для поддержки уничтожения / завершения объекта, поскольку они знают со 100% уверенностью, когда объект больше не используется. Тем не менее, большинство из этих языков не поддерживают его.

Я не думаю, что это тот случай, когда деструктор никогда не будет вызван, так как это будет утечка памяти ядра, которую gcs разработан, чтобы избежать. Я мог видеть возможный аргумент, заключающийся в том, что деструктор / финализатор может быть вызван только в течение неопределенного времени в будущем, но это не помешало Java или Python поддерживать функциональность.

Каковы основные причины разработки языка, чтобы не поддерживать какую-либо форму завершения объекта?


9
Может потому что finalize/ destroyэто ложь? Нет гарантии, что это когда-либо будет выполнено. И даже если вы не знаете, когда (с учетом автоматической сборки мусора) и, если необходимо, контекст все еще существует (возможно, он уже был собран). Поэтому безопаснее обеспечить согласованное состояние другими способами, и можно заставить программиста сделать это.
Рафаэль

1
Я думаю, что этот вопрос является пограничным оффтопом. Это вопрос дизайна языка программирования, который мы хотим развлечь, или это вопрос сайта, более ориентированного на программирование? Голосование сообщества, пожалуйста.
Рафаэль

14
Это хороший вопрос в дизайне PL, давайте иметь его.
Андрей Бауэр

3
Это на самом деле не статическое / динамическое различие. Многие статические языки не имеют финализаторов. На самом деле, разве языки не с финализаторами в меньшинстве?
Андрей Бауэр

1
думаю, здесь есть какой-то вопрос ... было бы лучше, если бы вы определили термины немного больше. В Java есть блок finally, который связан не с уничтожением объекта, а с выходом метода. Есть и другие способы борьбы с ресурсами. Например, в Java пул соединений может иметь дело с соединениями, которые не используются [x] времени, и восстанавливать их. не элегантно, но это работает. Отчасти ответ на ваш вопрос заключается в том, что сборка мусора - это, в основном, недетерминированный, не мгновенный процесс, и он не управляется объектами, которые больше не используются, а вызванными ограничениями / потолками памяти.
ВЗН

Ответы:


10

Шаблон, о котором вы говорите, когда объекты знают, как очистить свои ресурсы, попадает в три соответствующие категории. Давайте не будем связывать деструкторы с финализаторами - только один связан с сборкой мусора:

  • Модели финализатора : метод очистки автоматически объявляется, определяется программистом, вызывается автоматически.

    Финализаторы вызываются автоматически перед освобождением сборщиком мусора. Этот термин применяется, если используемый алгоритм сборки мусора может определять жизненные циклы объекта.

  • Модели деструктора : метод очистки автоматически объявляется, определяется программистом, вызывается автоматически только иногда.

    Деструкторы могут вызываться автоматически для объектов, выделенных стеком (поскольку время жизни объекта является детерминированным), но должны вызываться явным образом на всех возможных путях выполнения для объектов, распределенных в куче (поскольку время жизни объекта является недетерминированным).

  • Шаблон Измельчитель : очистка метод , объявленный, определяется, и называется программистом.

    Программисты создают метод удаления и вызывают его сами - вот где myObject.destroy()падает ваш пользовательский метод. Если утилизация абсолютно необходима, тогда утилиты должны вызываться на всех возможных путях выполнения.

Финализаторы - это дроиды, которых вы ищете.

Шаблон финализатора (шаблон, о котором спрашивает ваш вопрос) - это механизм связывания объектов с системными ресурсами (сокеты, файловые дескрипторы и т. Д.) Для взаимного восстановления сборщиком мусора. Но финализаторы в основном зависят от используемого алгоритма сборки мусора.

Рассмотрим это ваше предположение:

Языки, которые предлагают автоматическую сборку мусора ... знают со 100% уверенностью, когда объект больше не используется.

Технически неверно (спасибо, @babou). Сборка мусора в основном касается памяти, а не объектов. Если алгоритм сбора данных обнаружит, что память объекта больше не используется, зависит от алгоритма и (возможно) от того, как ваши объекты ссылаются друг на друга. Давайте поговорим о двух типах сборщиков мусора во время выполнения. Есть много способов изменить и дополнить их базовыми методами:

  1. Отслеживание GC. Эти следы памяти, а не объекты. Если они не увеличены, они не сохраняют обратных ссылок на объекты из памяти. Если они не дополнены, эти GC не будут знать, когда объект может быть завершен, даже если они знают, когда его память недоступна. Следовательно, вызовы финализатора не гарантируются.

  2. Подсчет ссылок GC . Они используют объекты для отслеживания памяти. Они моделируют достижимость объекта с помощью ориентированного графа ссылок. Если в вашем графе ссылок объектов есть цикл, то для всех объектов в цикле никогда не будет вызываться их финализатор (очевидно, до завершения программы). Опять же, вызовы финализатора не гарантируются.

TLDR

Сборка мусора сложна и разнообразна. Вызов финализатора не может быть гарантирован до завершения программы.


Вы правы, что это не статический v. Динамический. Это проблема с языками для сбора мусора. Сборка мусора является сложной проблемой и, вероятно, является основной причиной, поскольку необходимо учитывать множество крайних случаев (например, что произойдет, если логика finalize()приведет к повторной ссылке на очищаемый объект?). Однако неспособность гарантировать вызов финализатора до завершения программы не помешала Java поддерживать его. Не сказать, что ваш ответ неправильный, просто, возможно, неполный. Все еще очень хороший пост. Спасибо.
dbcb

Спасибо за ответ. Вот попытка завершить мой ответ: явно опуская финализаторы, язык заставляет своих пользователей управлять своими собственными ресурсами. Для многих типов проблем это, вероятно, недостаток. Лично я предпочитаю выбор Java, потому что у меня есть возможности финализаторов, и ничто не мешает мне писать и использовать свой собственный утилизатор. Ява говорит: «Привет, программист. Ты не идиот, так что вот финализатор. Просто будь осторожен».
kdbanman 25.01.15

1
Обновил мой первоначальный вопрос, чтобы отразить, что это касается языков, собираемых мусором. Принимая ваш ответ. Спасибо, что нашли время ответить.
dbcb

Рад был помочь. Уточнение моего комментария сделало мой ответ более ясным?
kdbanman

2
Хорошо. Для меня реальный ответ здесь заключается в том, что языки предпочитают не реализовывать его, потому что воспринимаемая ценность не перевешивает проблемы реализации функциональности. Это не невозможно (как демонстрируют Java и Python), но есть компромисс, который многие языки предпочитают не делать.
декабря

5

В двух словах

Завершение не является простым делом для сборщиков мусора. Его легко использовать с GC для подсчета ссылок, но это семейство GC часто неполное, требующее компенсации утечек памяти путем явного запуска уничтожения и завершения некоторых объектов и структур. Отслеживание сборщиков мусора гораздо эффективнее, но значительно усложняет идентификацию объекта, подлежащего доработке и уничтожению, в отличие от простого определения неиспользуемой памяти, что требует более сложного управления, затрат времени и пространства и сложности реализация.

Введение

Я предполагаю, что вы спрашиваете, почему языки сборки мусора не обрабатывают автоматически уничтожение / финализацию в процессе сбора мусора, как указано в замечании:

Мне крайне не хватает того, что эти языки рассматривают память как единственный ресурс, которым стоит управлять. Как насчет сокетов, файловых дескрипторов, состояний приложений?

Я не согласен с принятым ответом, данным kdbanman . Хотя изложенные факты в основном верны, хотя и сильно склонны к подсчету ссылок, я не верю, что они правильно объясняют ситуацию, о которой пойдет речь в этом вопросе.

Я не верю, что терминология, разработанная в этом ответе, является большой проблемой, и она с большей вероятностью может сбить с толку. Действительно, как представлено, терминология в основном определяется способом активации процедур, а не тем, что они делают. Дело в том, что во всех случаях необходимо завершить объект, который больше не нужен, с помощью некоторого процесса очистки и освободить все ресурсы, которые он использовал, память является лишь одним из них. В идеале все это должно выполняться автоматически, когда объект больше не используется, с помощью сборщика мусора. На практике GC может отсутствовать или иметь недостатки, и это компенсируется явным срабатыванием программой доработки и восстановления.

Явное тригеринг в программе является проблемой, так как он может затруднить анализ ошибок программирования, когда объект, который все еще используется, явно завершается.

Следовательно, гораздо лучше полагаться на автоматическую сборку мусора для восстановления ресурсов. Но есть две проблемы:

  • некоторые методы сбора мусора допускают утечки памяти, которые препятствуют полной утилизации ресурсов. Это хорошо известно для подсчета ссылок GC, но может появиться для других методов GC при использовании некоторых организаций данных без осторожности (точка не обсуждается здесь).

  • в то время как методика GC может быть полезна для идентификации ресурсов памяти, которые больше не используются, финализация объектов, содержащихся в них, может быть непростой, и это усложняет проблему восстановления других ресурсов, используемых этими объектами, что часто является целью завершения.

Наконец, важный момент, который часто забывают, заключается в том, что циклы GC могут быть вызваны чем угодно, не только нехваткой памяти, если предусмотрены надлежащие зацепки и если стоимость цикла GC считается оправданной. Следовательно, вполне нормально инициировать сборщик мусора, когда отсутствует какой-либо ресурс, в надежде освободить его.

Подсчет ссылок сборщиков мусора

Подсчет ссылок является слабой техникой сбора мусора , которая не будет правильно обрабатывать циклы. Он действительно будет слаб при разрушении устаревших структур и восстановлении других ресурсов просто потому, что он слаб при восстановлении памяти. Но финализаторы легче всего использовать с сборщиком мусора (GC) подсчета ссылок, поскольку GC ref-count восстанавливает структуру, когда его ref ref уменьшается до 0, когда его адрес известен вместе с его типом, либо статически или динамически. Следовательно, можно восстановить память точно после применения правильного финализатора и рекурсивного вызова процесса для всех указанных объектов (возможно, через процедуру финализации).

В двух словах, финализация легко реализуется с помощью RefCing GC, но страдает от «незавершенности» этого GC, действительно из-за круговых структур, в той же степени, в какой страдает восстановление памяти. Другими словами, при подсчете ссылок память управляется так же плохо, как и другие ресурсы, такие как сокеты, файловые дескрипторы и т. Д.

Действительно, неспособность Ref Count GC восстанавливать циклические структуры (в общем) может рассматриваться как утечка памяти . Вы не можете ожидать, что все GC, чтобы избежать утечек памяти. Это зависит от алгоритма GC и от динамически доступной информации о структуре типов (например, в консервативном GC ).

Отслеживание сборщиков мусора

Более мощное семейство GC, без таких утечек, - это семейство трассировки, которое исследует живые части памяти, начиная с хорошо идентифицированных корневых указателей. Все части памяти, которые не посещаются в этом процессе трассировки (которые на самом деле могут быть разложены различными способами, но я должен упростить), являются неиспользуемыми частями памяти, которые могут быть таким образом восстановлены 1 . Эти сборщики восстановят все части памяти, к которым программа больше не сможет получить доступ, независимо от того, что она делает. Он восстанавливает круговые структуры, и более продвинутые GC основаны на некоторой вариации этой парадигмы, иногда очень сложной. В некоторых случаях его можно комбинировать с подсчетом ссылок и компенсировать его недостатки.

Проблема в том, что ваше утверждение (в конце вопроса):

Языки, которые предлагают автоматическую сборку мусора, являются основными кандидатами для поддержки уничтожения / завершения объекта, поскольку они знают со 100% уверенностью, когда объект больше не используется.

технически некорректно для отслеживания коллекторов.

Что известно со 100% уверенностью, это то, какие части памяти больше не используются . (Точнее, следует сказать, что они больше недоступны , поскольку некоторые части, которые больше не могут использоваться в соответствии с логикой программы, все еще считаются используемыми, если в программе все еще есть бесполезный указатель на них. данные.) Но необходима дальнейшая обработка и соответствующие структуры, чтобы узнать, какие неиспользуемые объекты могли храниться в этих теперь неиспользуемых частях памяти . Это не может быть определено из того, что известно о программе, так как программа больше не связана с этими частями памяти.

Таким образом, после прохода сборки мусора у вас остаются фрагменты памяти, которые содержат объекты, которые больше не используются, но априори нет способа узнать, что это за объекты, чтобы применить правильную финализацию. Кроме того, если коллектор трассировки является типом метки и развертки, может случиться так, что некоторые фрагменты могут содержать объекты, которые уже были завершены в предыдущем проходе GC, но с тех пор не использовались по причинам фрагментации. Однако это можно решить с помощью расширенной явной типизации.

В то время как простой сборщик просто восстановил бы эти фрагменты памяти, без дальнейших церемоний, для завершения требуется специальный проход, чтобы исследовать эту неиспользуемую память, идентифицировать содержащиеся в ней объекты и применять процедуры финализации. Но такое исследование требует определения типа объектов, которые там хранились, и определение типа также необходимо для применения надлежащего завершения, если таковое имеется.

Таким образом, это подразумевает дополнительные затраты времени GC (дополнительный проход) и, возможно, дополнительные затраты памяти, чтобы сделать правильную информацию о типе доступной во время этого прохода различными способами. Эти затраты могут быть значительными, поскольку часто требуется завершить работу только с несколькими объектами, тогда как время и пространство могут касаться всех объектов.

Другой момент заключается в том, что временные и пространственные накладные расходы могут касаться выполнения программного кода, а не только выполнения GC.

Я не могу дать более точный ответ, указывая на конкретные вопросы, потому что я не знаю специфику многих языков, которые вы перечислите. В случае с C печатание - очень сложная проблема, которая ведет к развитию консервативных коллекционеров. Я думаю, что это влияет и на C ++, но я не специалист по C ++. Это, кажется, подтверждается Гансом Бёмом, который проводил большую часть исследований по консервативному ГК. Консервативный GC не может систематически восстанавливать всю неиспользуемую память именно потому, что может отсутствовать точная информация о типе данных. По той же причине он не сможет систематически применять процедуры завершения.

Таким образом, можно делать то, что вы просите, как вы знаете из некоторых языков. Но это не бесплатно. В зависимости от языка и его реализации, это может повлечь за собой затраты, даже если вы не используете эту функцию. Различные методы и компромиссы могут рассматриваться для решения этих проблем, но это выходит за рамки разумного размера ответа.

1 - это абстрактное представление коллекции трассировки (включающее в себя как ГХ для копирования, так и для разметки и очистки), все меняется в зависимости от типа сборщика трассировки, а исследование неиспользуемой части памяти различается в зависимости от того, копируется или помечается и развертка используется.


Вы даете много хороших деталей о сборке мусора. Тем не менее, ваш ответ на самом деле не противоречит моему - ваш реферат и мой TLDR по сути говорят одно и то же. И что бы это ни стоило, мой ответ использует подсчет ссылок GC в качестве примера, а не "сильный уклон".
kdbanman

Прочитав более внимательно, я вижу разногласия. Я буду редактировать соответственно. Кроме того, моя терминология должна была быть однозначной. Речь шла о том, чтобы объединить финализаторы и деструкторы и даже упомянуть распорядителей на одном дыхании. Стоит распространять правильные слова.
kdbanman

@kdbanman Трудность заключалась в том, что я обращался к вам обоим, так как ваш ответ стоял в качестве ссылки. Вы не можете использовать ref count в качестве примера парадигмы, потому что это слабый GC, редко используемый в языках (проверьте языки, на которые ссылается OP), для которых добавление финализаторов было бы действительно легким (но с ограниченным использованием). Трассировочные коллекторы почти всегда используются. Но финализаторы трудно зацепить их, потому что умирающие объекты неизвестны (вопреки утверждению, которое вы считаете правильным). Различие между статической и динамической типизацией не имеет значения, поскольку динамическая типизация хранилища данных имеет важное значение.
Бабу

@kdbanman Что касается терминологии, в целом она полезна, поскольку соответствует различным ситуациям. Но и здесь это не помогает, поскольку речь идет о передаче доработки в GC. Основной GC должен делать только уничтожение. Необходима терминология, которая различает getting memory recycled, которую я называю reclamation, и выполняю некоторую очистку перед этим, такую ​​как восстановление других ресурсов или обновление некоторых таблиц объектов, которые я вызываю finalization. Это казалось мне актуальными вопросами, но я, возможно, упустил момент в вашей терминологии, который был для меня новым.
Бабу

1
Спасибо @kdbanman, бабу. Хорошая дискуссия. Я думаю, что оба ваших поста охватывают аналогичные моменты. Как вы оба указали, основной проблемой, по-видимому, является категория сборщика мусора, используемого во время выполнения языка. Я нашел эту статью , которая проясняет некоторые заблуждения для меня. Кажется, более надежные gcs обрабатывают только низкоуровневую необработанную память, что делает типы объектов более высокого уровня непрозрачными для gc. Без знания внутренних элементов памяти, gc не может разрушать объекты. Который, кажется, ваш вывод.
декабря

4

Шаблон деструктора объекта является фундаментальным для обработки ошибок в системном программировании, но не имеет ничего общего с сборкой мусора. Скорее, это связано с сопоставлением времени жизни объекта с областью видимости и может быть реализовано / использовано на любом языке, который имеет функции первого класса.

Пример (псевдокод). Предположим, у вас есть тип «сырой файл», такой как тип дескриптора файла Posix. Есть четыре основных операций, open(), close(), read(), write(). Вы хотели бы реализовать «безопасный» тип файла, который всегда очищается после себя. (То есть, у этого есть автоматический конструктор и деструктор.)

Я предполагаю, что на нашем языке есть обработка исключений throw, tryи finally(на языках без обработки исключений вы можете установить дисциплину, в которой пользователь вашего типа возвращает специальное значение для обозначения ошибки.)

Вы устанавливаете функцию, которая принимает функцию, которая выполняет работу. Рабочая функция принимает один аргумент (дескриптор «безопасного» файла).

with_file_opened_for_read (string:   filename,
                           function: worker_function(safe_file f)):
  raw_file rf = open(filename, O_RDONLY)
  if rf == error:
    throw File_Open_Error

  try:
    worker_function(rf)
  finally:
    close(rf)

Вы также предоставляете реализации read()и write()для safe_file(которые просто вызывают raw_file read()и write()). Теперь пользователь использует такой safe_fileтип:

...
with_file_opened_for_read ("myfile.txt",
                           anonymous_function(safe_file f):
                             mytext = read(f)
                             ... (including perhaps throwing an error)
                          )

Деструктор C ++ - это просто синтаксический сахар для try-finallyблока. Практически все, что я здесь сделал, - это конвертировал то, во что safe_fileбудет компилироваться класс C ++ с конструктором и деструктором. Обратите внимание, что в C ++ нет finallyисключений, в частности, потому что Страуструп считал, что использование явного деструктора лучше синтаксически (и он ввел его в язык до того, как в нем появились анонимные функции).

(Это упрощение одного из способов, которыми люди занимались обработкой ошибок в Lisp-подобных языках в течение многих лет. Я думаю, что впервые столкнулся с этим в конце 1980-х или начале 1990-х, но я не помню, где.)


Это описывает внутреннюю структуру шаблона деструктора на основе стека в C ++, но не объясняет, почему язык с мусором не реализует такую ​​функциональность. Возможно, вы правы, что это не имеет ничего общего со сборкой мусора, но это связано с общим уничтожением / завершением объекта, которое кажется трудным или неэффективным в языках сборки мусора. Так что, если общее уничтожение не поддерживается, уничтожение на основе стека, похоже, также будет опущено.
dbcb

Как я уже говорил в начале: любой язык с сборкой мусора, имеющий функции первого класса (или некоторое приближение функций первого класса), дает вам возможность предоставлять «пуленепробиваемые» интерфейсы, такие как safe_fileи with_file_opened_for_read(объект, который закрывается, когда выходит из области видимости ). Важно то, что он не имеет тот же синтаксис, что и конструкторы, не имеет значения. Lisp, Scheme, Java, Scala, Go, Haskell, Rust, Javascript, Clojure поддерживают достаточное количество функций первого класса, поэтому им не нужны деструкторы для предоставления той же полезной функции.
Блуждающая логика

Я думаю, я понимаю, что вы говорите. Поскольку языки предоставляют базовые строительные блоки (try / catch / finally, функции первого класса и т. Д.) Для ручной реализации функциональности, подобной деструктору, им не нужны деструкторы? Я мог видеть некоторые языки по этому пути по причинам простоты. Хотя кажется маловероятным, что это основная причина для всех перечисленных языков, но, возможно, именно так оно и есть. Может быть, я просто в подавляющем меньшинстве, которые любят деструкторы C ++, и никого больше это не волнует, что вполне может быть причиной того, что большинство языков не реализуют деструкторы. Им просто все равно.
dbcb

2

Это не полный ответ на вопрос, но я хотел добавить пару замечаний, которые не были отражены в других ответах или комментариях.

  1. Вопрос неявно предполагает, что мы говорим об объектно-ориентированном языке в стиле Simula, который сам по себе является ограничением. В большинстве языков, даже с объектами, не все является объектом. Механизм реализации деструкторов налагает затраты, которые не каждый плательщик языка готов заплатить.

  2. C ++ имеет некоторые неявные гарантии порядка уничтожения. Например, если у вас есть древовидная структура данных, дочерние элементы будут уничтожены раньше, чем родительские. Это не относится к языкам GC, поэтому иерархические ресурсы могут высвобождаться в непредсказуемом порядке. Для ресурсов без памяти это может иметь значение.


2

Когда разрабатывались две наиболее популярные платформы GC (Java и .NET), я думаю, что авторы ожидали, что финализация будет работать достаточно хорошо, чтобы избежать необходимости использования других форм управления ресурсами. Многие аспекты языкового и каркасного проектирования могут быть значительно упрощены, если нет необходимости во всех функциях, необходимых для обеспечения 100% надежного и детерминированного управления ресурсами. В C ++ необходимо различать понятия:

  1. Указатель / ссылка, который идентифицирует объект, который принадлежит исключительно владельцу ссылки, и который не идентифицирован никакими указателями / ссылками, о которых владелец не знает.

  2. Указатель / ссылка, которая идентифицирует разделяемый объект, который никому не принадлежит.

  3. Указатель / ссылка, который идентифицирует объект, который принадлежит исключительно владельцу ссылки, но к которому можно получить доступ через «представления», владелец не может отслеживать.

  4. Указатель / ссылка, которая идентифицирует объект, который обеспечивает представление объекта, который принадлежит кому-то еще.

Если языку / структуре GC не нужно беспокоиться об управлении ресурсами, все вышеперечисленное может быть заменено одним видом ссылки.

Я нахожу наивной идею, что завершение устранит необходимость в других формах управления ресурсами, но независимо от того, было ли такое ожидание разумным в то время, история показала, что во многих случаях требуется более точное управление ресурсами, чем обеспечивает завершение. , Мне кажется, что вознаграждение за признание владения на уровне языка / структуры было бы достаточно, чтобы оправдать стоимость (сложность должна где-то существовать, а перемещение ее на язык / структуру упростило бы пользовательский код), но признаю, что существуют значительные Преимущества разработки - наличие единственного «вида» ссылок - то, что работает, только если язык / структура не зависят от вопросов очистки ресурсов.


2

Почему парадигма деструктора объекта в языках с мусорным сбором повсеместно отсутствует?

Я родом из C ++, так что эта область сбивает меня с толку.

Деструктор в C ++ фактически делает две вещи вместе взятыми. Это освобождает оперативную память и освобождает идентификаторы ресурсов.

Другие языки разделяют эти проблемы тем, что GC отвечает за освобождение ОЗУ, в то время как другая функция языка отвечает за освобождение идентификаторов ресурсов.

Мне крайне не хватает того, что эти языки рассматривают память как единственный ресурс, которым стоит управлять.

Вот что такое GC. Они делают только одну вещь, и это гарантирует, что вам не хватит памяти. Если ОЗУ бесконечно, все GC будут удалены, так как больше нет никакой реальной причины их существования.

Как насчет сокетов, файловых дескрипторов, состояний приложений?

Языки могут предоставлять различные способы освобождения идентификаторов ресурсов путем:

  • руководство .CloseOrDispose()разбросано по коду

  • руководство .CloseOrDispose()разбросано внутри ручного " finallyблока"

  • ручные «блоки идентификаторов ресурсов» (т.е. using, with, try-с-ресурсы и т.д.) , который автоматизирует .CloseOrDispose()после того , как блок сделан

  • гарантированный «блок идентификатора ресурса», который автоматизируется.CloseOrDispose() после того, как блок сделан

Многие языки используют ручные (в отличие от гарантированных) механизмы, которые создают возможность для неэффективного управления ресурсами. Возьмите этот простой код NodeJS:

require('fs').openSync('file1.txt', 'w');
// forget to .closeSync the opened file

..где программист забыл закрыть открытый файл.

Пока программа продолжает работать, открытый файл будет зависать в подвешенном состоянии. Это легко проверить, попытавшись открыть файл с помощью HxD и убедившись, что это невозможно сделать:

введите описание изображения здесь

Освобождение идентификаторов ресурсов в деструкторах C ++ также не гарантируется. Вы можете подумать, что RAII работает как гарантированные «блоки идентификаторов ресурсов», но в отличие от «блоков идентификаторов ресурсов», язык C ++ не останавливает утечку объекта, предоставляющего блок RAII , поэтому блок RAII никогда не может быть выполнен .


Кажется, что почти все современные языки с сборкой мусора с поддержкой объектов OOPy, такие как Ruby, Javascript / ES6 / ES7, Actionscript, Lua и т. Д., Полностью опускают парадигму деструктора / финализации. Кажется, что Python - единственный __del__()метод со своим классом . Почему это?

Потому что они управляют идентификаторами ресурсов, используя другие способы, как указано выше.

Какие решения по проектированию языка приводят к тому, что эти языки не имеют возможности выполнять пользовательскую логику при удалении объектов?

Потому что они управляют идентификаторами ресурсов, используя другие способы, как указано выше.

Почему язык имеет встроенную концепцию экземпляров объектов с классом или классоподобными структурами вместе с пользовательскими экземплярами (конструкторами), но при этом полностью исключает функциональность уничтожения / финализации?

Потому что они управляют идентификаторами ресурсов, используя другие способы, как указано выше.

Я мог видеть возможный аргумент, заключающийся в том, что деструктор / финализатор может быть вызван только в течение неопределенного времени в будущем, но это не помешало Java или Python поддерживать функциональность.

У Java нет деструкторов.

Документы Java упоминают :

однако обычная цель finalize - выполнить действия по очистке до того, как объект будет безвозвратно удален. Например, метод finalize для объекта, который представляет соединение ввода-вывода, может выполнять явные транзакции ввода-вывода, чтобы разорвать соединение до того, как объект будет окончательно отброшен.

... но размещение кода управления идентификатором ресурса в Object.finalizerзначительной степени рассматривается как шаблон ( см. ). Этот код должен быть написан на сайте вызова.

Для людей, которые используют анти-шаблон, их оправдание состоит в том, что они могли забыть опубликовать идентификаторы ресурсов на сайте вызовов. Таким образом, они делают это снова в финализаторе, на всякий случай.

Каковы основные причины разработки языка, чтобы не поддерживать какую-либо форму завершения объекта?

Существует не так много вариантов использования для финализаторов, так как они используются для выполнения фрагмента кода между временем, когда больше нет сильных ссылок на объект, и временем, когда его память освобождается ГХ.

Возможный вариант использования - это когда вы хотите сохранить журнал времени между тем, как объект собран GC, и временем, когда больше нет сильных ссылок на объект, как таковое:

finalize() {
    Log(TimeNow() + ". Obj " + toString() + " is going to be memory-collected soon!"); // "soon"
}

-1

нашел ссылку на это в Dr Dobbs wrt c ++, в которой есть более общие идеи, утверждающие, что деструкторы проблематичны в языке, где они реализованы. Похоже, грубая идея заключается в том, что главная цель деструкторов - справиться с освобождением памяти, и это трудно сделать правильно. память распределяется по кусочкам, но разные объекты связываются, и тогда ответственность / границы освобождения становятся не столь ясными.

поэтому решение для сборщика мусора было разработано несколько лет назад, но сборка мусора основана не на объектах, исчезающих из области видимости при выходе из метода (это концептуальная идея, которую сложно реализовать), а на сборщике, работающем периодически, несколько недетерминированно, когда приложение испытывает «давление памяти» (т.е. не хватает памяти).

другими словами, простое человеческое понятие «недавно неиспользованный объект» на самом деле является в некотором смысле вводящей в заблуждение абстракцией в том смысле, что ни один объект не может «мгновенно» стать неиспользованным. неиспользуемые объекты могут быть «обнаружены» только при запуске алгоритма сборки мусора, который пересекает граф ссылок на объекты, и наилучшие алгоритмы запускаются периодически.

возможно, существует лучший алгоритм сбора мусора, ожидающий обнаружения, который может почти мгновенно идентифицировать неиспользуемые объекты, что может затем привести к согласованному коду вызова деструктора, но он не был найден после многих лет исследований в этой области.

Похоже, что решение областей управления ресурсами, таких как файлы или соединения, состоит в том, чтобы иметь «менеджеров» объектов, которые пытаются справиться с их использованием.


2
Интересная находка. Спасибо. Аргумент автора основан на неправильном вызове деструктора из-за передачи экземпляров класса по значению, когда у класса нет надлежащего конструктора копирования (что является реальной проблемой). Однако этот сценарий в действительности не существует в большинстве (если не во всех) современных динамических языках, потому что все передается по ссылке, что позволяет избежать ситуации автора. Хотя это и интересная перспектива, я не думаю, что она объясняет, почему большинство языков сборки мусора решили не использовать функциональность деструктора / финализации.
dbcb

2
Этот ответ искажает статью доктора Добба: в статье не утверждается, что деструкторы вообще проблематичны. В статье на самом деле утверждается следующее: примитивы управления памятью похожи на операторы goto, потому что они оба простые, но слишком мощные. Точно так же, как операторы goto лучше всего инкапсулируются в «соответствующим образом ограниченные структуры управления» (см .: Dijktsra), примитивы управления памятью лучше всего инкапсулируются в «соответствующим образом ограниченные структуры данных». Деструкторы - это шаг в этом направлении, но недостаточно далеко. Решите сами, правда ли это.
kdbanman
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.