сборка мусора для `fopen ()`? - PullRequest
0 голосов
/ 21 февраля 2019

Boehm gc занимается только выделением памяти.Но если кто-то хочет использовать сборщик мусора для работы с fopen(), так что fclose() больше не нужен.Есть ли способ сделать это в C?

PS Например, PyPy использует подход сборки мусора для открытия файлов.

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

http://doc.pypy.org/en/latest/cpython_differences.html

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

TL; DR: да, но.Больше, чем да.

Перво-наперво.Так как стандартная библиотека C сама должна автоматически собирать мусор с помощью открытых дескрипторов файлов в функции exit() (см. Стандартные кавычки ниже), необязательно когда-либо вызывать fclose, если:

  1. Вы абсолютно уверены, что ваша программа в конечном итоге прекратит работу, вернувшись с main() или вызвав exit().

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

  3. Вам не нужно получать информацию, если операция закрытия не удалась (возможно, из-засбой диска).

  4. Ваш процесс не откроет более FOPEN_MAX файлов и не будет пытаться открыть один и тот же файл дважды.(FOPEN_MAX должно быть не менее восьми, но это включает три стандартных потока.)

Конечно, кроме очень простых игрушечных приложений, эти гарантии довольно ограничительны, особенно для файловоткрыт для записи.Для начала, как вы собираетесь гарантировать, что хост не выйдет из строя или не отключится (условие отмены 1)?Поэтому большинство программистов считают очень плохим стилем не закрывать все открытые файлы.

Тем не менее, можно представить себе приложение, которое открывает файлы только для чтения.В этом случае наиболее серьезная проблема с никогда не вызывать fclose будет последним, ограничение одновременного открытия файла.Пять - это довольно небольшое число, и хотя большинство систем имеют гораздо более высокие ограничения, они почти все имеют ограничения;если приложение работает достаточно долго, оно неизбежно откроет слишком много файлов.(Условие 3 также может быть проблемой, хотя не все операционные системы налагают это ограничение, и лишь немногие системы налагают ограничение на файлы, открытые только для чтения.)

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

  1. Стандартная библиотека не обязана динамически размещать FILE объекты с использованием malloc или даже вообще динамически размещать их.(Например, библиотека, которая допускает только восемь открытых файлов, может иметь внутренний статически распределенный массив из восьми FILE структур.) Таким образом, сборщик мусора может никогда не увидеть распределения памяти.Чтобы задействовать сборщик мусора при удалении FILE объектов, каждый FILE* должен быть заключен в динамически размещаемый прокси («дескриптор»), и каждый интерфейс, который принимает или возвращает указатели FILE*, должен бытьзавернутый в тот, который создает прокси.Это не слишком много работы, но есть много интерфейсов, которые нужно обернуть, и использование оберток в основном зависит от модификации исходного кода;вам может быть сложно ввести FILE* прокси, если некоторые файлы открываются внешними библиотечными функциями.

  2. Хотя сборщику мусора можно сказать , что делать перед удалением определенных объектов (см. Ниже), большинство библиотек сборщика мусора не имеют интерфейса, который предусматривает ограничение на создание объектов, кроме доступностипамяти.Сборщик мусора может решить проблему «слишком много открытых файлов» только в том случае, если он знает, сколько файлов разрешено открывать одновременно, но не знает, и у него нет способа сообщить вам об этом.Таким образом, вы должны договориться о том, чтобы сборщик мусора вызывался вручную, когда этот предел вот-вот будет нарушен.Конечно, поскольку вы уже заключаете все вызовы в fopen, как указано в пункте 1, вы можете добавить эту логику в свою оболочку, либо отслеживая количество открытых файлов, либо реагируя на индикацию ошибки из fopen().(Стандарт C не определяет переносимый механизм для обнаружения этой конкретной ошибки, но Posix говорит, что fopen должен завершиться ошибкой, и установите errno на EMFILE, если в процессе слишком много открытых файлов. Posix также определяет ENFILE значение ошибки для случая, когда слишком много файлов открыто во всех процессах, вероятно, стоит рассмотреть оба этих случая.)

  3. Кроме того, сборщик мусора не 'не может быть механизма для ограничения сбора мусора одним типом ресурса.(Было бы очень трудно реализовать это в сборщике мусора с разметкой меток, таком как сборщик BDW, потому что вся используемая память должна быть отсканирована для поиска живых указателей.) Таким образом, запуск сборки мусора при каждом использовании всех слотов дескриптора файла можетОказывается, это довольно дорого.

  4. Наконец, сборщик мусора не гарантирует своевременный сбор мусора.Если ресурсов не будет, сборщик мусора может оставаться в неактивном состоянии в течение длительного времени, и если вы используете сборщик мусора для закрытия файлов, это означает, что файлы могут оставаться открытыми в течение неограниченного времени, даже если онибольше не используется.Таким образом, первые два условия в первоначальном списке требований об исключении fclose() продолжают действовать даже при использовании сборщика мусора.

Итак.Да, но, но, но, но.Вот что рекомендует документация Boehm GC (сокращенно):

  • Действия, которые должны быть выполнены незамедлительно… должны обрабатываться явными вызовами в коде.когда бы ни было удобно.Используйте [сборщик мусора] только в качестве механизма резервного копирования для случаев, которые трудно обрабатывать явно.
  • Если скудные ресурсы управляются с помощью [сборщика мусора], процедура выделения для этого ресурса (например, дескрипторы открытых файлов)) должен инициировать сборку мусора (два, если этого недостаточно), если ему не хватает ресурса.
  • Если управление чрезвычайно скудными ресурсами (например, файловые дескрипторы в системах с ограничением в 20 открытых файлов)), возможно, потребуется ввести схему кэширования дескриптора, чтобы скрыть ограничение ресурсов.

Теперь предположим, что вы все это прочитали и все еще хотите это сделать.Это на самом деле довольно просто.Как упоминалось выше, вам нужно определить прокси-объект или дескриптор, который содержит FILE*.(Если вы используете интерфейсы Posix, такие как open(), которые используют файловые дескрипторы - маленькие целые числа - вместо структур FILE, то дескриптор содержит fd. Это, очевидно, другой тип объекта, но механизм идентичен.)

В вашей оболочке для fopen() (или open() или любых других вызовов, которые возвращают open FILE* s или файлы), вы динамически выделяете дескриптор, а затем (в случаевызов Boehm GC) GC_register_finalizer, чтобы сообщить сборщику мусора, какую функцию вызывать, когда ресурс будет удален.Почти все библиотеки GC имеют такие возможности;поиск finalizer в их документации.Вот документация для сборщика Boehm , из которой я извлек список предупреждений выше.

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

  1. Динамическое выделение дескриптора.
  2. Инициализация его содержимого для значения часового значения (например, -1 или NULL), которое указывает, что дескриптор еще не былназначен открытому файлу.
  3. Зарегистрируйте финализатор для дескриптора.Функция финализатора должна проверять значение дозорного перед попыткой вызова fclose(), поэтому регистрация дескриптора в этот момент вполне подходит.
  4. Откройте файл (или другой такой ресурс).
  5. Еслиоткрытие успешно, сбросить дескриптор, чтобы использовать возвращенный из открытия.Если ошибка связана с исчерпанием ресурсов, запустите сборку мусора вручную и повторите при необходимости.(Будьте осторожны, чтобы ограничить количество раз, которое вы делаете это для одной открытой оболочки. Иногда вам нужно сделать это дважды, но три последовательных сбоя, вероятно, указывают на какую-то другую проблему.)
  6. Если открытие в конце концов завершилось успешноВерните ручку.В противном случае, необязательно отмените регистрацию финализатора (если это позволяет ваша библиотека GC) и верните сообщение об ошибке.

Обязательные стандартные кавычки C

  1. Возврат изmain() аналогично вызову exit()

    §5.1.2.2.3 (завершение программы): (относится только к размещенным реализациям)

    1. Если возвращенотип функции main является типом, совместимым с int, возврат из начального вызова функции main эквивалентен вызову функции выхода со значением, возвращаемым функцией main в качестве аргумента;достижение }, которое завершает функцию main, возвращает значение 0.
  2. Вызов exit() очищает все файловые буферы и закрывает все открытые файлы

    §7.22.4.4 (функция выхода):

    Затем все открытые потоки с неписанными буферизованными данными сбрасываются, все открытые потоки закрываются и все файлы, созданные с помощью функции tmpfile, удаляются…
0 голосов
/ 21 февраля 2019

В случае, если это не очевидно, ничего Boehm GC делает это возможным в C. Вся библиотека представляет собой огромную кучу неопределенного поведения, которое вроде бы работает в некоторых (многих?) Реальных реализациях.Чем более продвинуты , особенно в области безопасности , реализации C, тем меньше вероятность того, что какая-либо из них будет продолжать работать.

С учетом сказанного я не вижу никакой причинытот же принцип не может быть расширен до FILE* ручек.Проблема, однако, заключается в том, что из-за того, что он обязательно является консервативным GC, ложные срабатывания для оставшихся ссылок будут препятствовать закрытию файла, что имеет видимые последствия для состояния процесса и файловой системы.Если вы явно fflush в нужных местах, это может быть приемлемо только наполовину разбито, однако.

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

...