是否有必要在ruby中关闭StringIO?

Evg*_*nii 14 ruby

我们是否需要在Ruby中使用后关闭StringIO对象以释放资源,就像我们使用真正的IO对象一样?

obj = StringIO.new "some string"
#...
obj.close # <--- Do we need to close it?
Run Code Online (Sandbox Code Playgroud)

提炼我的问题

关闭File对象是必要的,因为它将关闭文件描述符.操作系统中打开的文件数量有限,这就是必须关闭文件的原因.但是,如果我理解正确,StringIO是内存中的抽象.我们需要关闭吗?

Way*_*rad 21

  • 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;
};
Run Code Online (Sandbox Code Playgroud)

当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;
}
Run Code Online (Sandbox Code Playgroud)

只有在对象被垃圾回收时才会调用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;
}
Run Code Online (Sandbox Code Playgroud)

最后,当您调用时StringIO#string,您将获得对StringIO实例累积的完全相同的字符串的引用:

static VALUE
strio_get_string(VALUE self)
{   
    return StringIO(self)->string;
}
Run Code Online (Sandbox Code Playgroud)

使用StringIO时如何泄漏内存

所有这一切都意味着StringIO实例只有一种方法可以导致资源泄漏:您不能关闭StringIO对象,并且必须将其保留的时间长于保留调用时所获得的字符串StringIO#string.例如,假设一个具有StringIO对象作为实例变量的类:

class Leaker

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

  def result
    @sio.string
  end

end
Run Code Online (Sandbox Code Playgroud)

想象一下,这个类的用户获取结果,简单地使用它,然后丢弃它,然后保留对Leaker实例的引用.您可以看到Leaker实例通过未关闭的StringIO实例保留对结果的引用.如果文件非常大,或者存在许多现存的Leaker实例,这可能是一个问题.这个简单(故意病态)的例子可以通过简单地不将StringIO保持为实例变量来解决.当你可以(而且你几乎总是可以)时,最好简单地扔掉StringIO对象而不是明确地关闭它的麻烦:

class NotALeaker

  attr_reader :result

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

end
Run Code Online (Sandbox Code Playgroud)

除此之外,这些泄漏只在字符串很大或者StringIO实例很多并且 StringIO实例很长时才有用,你可以看到显然关闭StringIO很少,如果有的话,也是如此.