Нужно ли закрывать StringIO в рубине?

Нужно ли закрывать объекты StringIO после использования в Ruby, чтобы освободить ресурсы, например, с реальными объектами IO?

obj = StringIO.new "some string"
#...
obj.close # <--- Do we need to close it?

Уточнение моего вопроса

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

Ответ 1

  • StringIO#close не освобождает ресурсы или не отбрасывает ссылку на накопленную строку. Поэтому вызов его не влияет на использование ресурсов.

  • Только StringIO#finalize, вызываемый во время сбора мусора, освобождает ссылку на накопленную строку, чтобы ее можно было освободить (при условии, что вызывающий абонент не сохраняет свою собственную ссылку на нее).

  • StringIO.open, который кратко создает экземпляры StringIO, не сохраняет ссылку на этот экземпляр после его возвращения; поэтому ссылка StringIO на накопленную строку может быть освобождена (если вызывающий не сохраняет свою собственную ссылку на нее).

  • В практическом плане редко приходится беспокоиться об утечке памяти при использовании StringIO. Просто не держитесь за ссылки на StringIO, как только вы закончите с ними, и все будет хорошо.


Погружение в источник

Единственный ресурс, используемый экземпляром StringIO, - это строка, которую он накапливает. Вы можете видеть это в stringio.c(MRI 1.9.3); здесь мы видим структуру, которая содержит состояние StringIO:

static struct StringIO *struct StringIO {
    VALUE string;
    long pos;
    long lineno;
    int flags;
    int count;
};

Когда экземпляр StringIO завершен (т.е. собран мусор), его ссылка на строку отбрасывается, так что строка может быть собрана в мусор, если нет других ссылок на нее. Здесь метод finalize, который также называется StringIO#open(&block), чтобы закрыть экземпляр.

static VALUE
strio_finalize(VALUE self)
{
    struct StringIO *ptr = StringIO(self);
    ptr->string = Qnil;
    ptr->flags &= ~FMODE_READWRITE;
    return self;
}

Метод finalize вызывается только тогда, когда объект собирает мусор. Нет другого метода StringIO, который освобождает ссылку на строку.

StringIO#close просто устанавливает флаг. Он не освобождает ссылку на накопленную строку или каким-либо другим образом влияет на использование ресурсов:

static VALUE
strio_close(VALUE self)
{   
    struct StringIO *ptr = StringIO(self);
    if (CLOSED(ptr)) {
        rb_raise(rb_eIOError, "closed stream");
    }
    ptr->flags &= ~FMODE_READWRITE;
    return Qnil;
}

И наконец, когда вы вызываете StringIO#string, вы получаете ссылку на ту же строку, что и экземпляр StringIO:

static VALUE
strio_get_string(VALUE self)
{   
    return StringIO(self)->string;
}

Как утечка памяти при использовании StringIO

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

class Leaker

  def initialize
    @sio = StringIO.new
    @sio.puts "Here a large file:"
    @sio.puts
    @sio.write File.read('/path/to/a/very/big/file')
  end

  def result
    @sio.string
  end

end

Представьте, что пользователь этого класса получает результат, использует его ненадолго, а затем отбрасывает его и все же сохраняет ссылку на экземпляр Leaker. Вы можете видеть, что экземпляр Leaker сохраняет ссылку на результат через незаблокированный экземпляр StringIO. Это может быть проблемой, если файл очень велик, или если существует много существующих экземпляров Leaker. Этот простой (и преднамеренно патологический) пример можно зафиксировать, просто не сохраняя StringIO в качестве переменной экземпляра. Когда вы можете (и вы почти всегда можете), лучше просто выбросить объект StringIO, а не пытаться закрыть его явно:

class NotALeaker

  attr_reader :result

  def initialize
    sio = StringIO.new
    sio.puts "Here a large file:"
    sio.puts
    sio.write File.read('/path/to/a/very/big/file')
    @result = sio.string
  end

end

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

Ответ 2

Когда вы закрываете File, это важно, поскольку система имеет ограниченное количество дескрипторов (при условии, что вы работаете в UNIX, я не знаю, что делает Windows). С StringIO на карту поставлен еще один ресурс: Память. Если объект StringIO имеет много возможностей для хранения, в куче будет много памяти. С другой стороны, сборщик мусора всегда закрывает объекты IO, которые он утверждает, но предполагает, что у вас есть элегантный способ вывести объект из сферы действия. Кроме того, в сильно загруженной системе физическое ОЗУ гораздо более ценно, чем дескрипторы файлов. На моем Linux-поле 178203 - это максимальные файловые дескрипторы. Я сомневаюсь, что вы могли бы достичь этого.

Ответ 3

В ответ на другие ответы здесь: вызов close не поможет вам сохранить память.

require "stringio"
sio = StringIO.new
sio.print("A really long string")
sio.close
sio.string # => "A really long string"

"Действительно длинная строка" будет оставаться до тех пор, пока sio делает, close или нет close.

Итак, почему StringIO имеет метод close? Утиная печать. Предоставляя close и бросая IOError, если вы пытаетесь его прочитать или написать, гарантирует, что он будет действовать как реальный объект File. Это полезно, если вы используете его как макет объекта во время модульного тестирования.

Ответ 4

В общем, ответ - нет. Потоки ввода-вывода автоматически закрываются, когда заявляется сборщик мусора. Тот же ответ на файл ввода/вывода также.

Ответ 5

Нет, но это полезно для оптимизации памяти