在Ruby中为互斥对象使用类实例变量



注意:下面显示的代码摘要是我遇到问题的代码的提炼。由于其他人已经回答了,我在这里留下了这个原始摘要,但实际的代码显示在我下面提供的答案中。

我还没能把它与一个小的失败测试用例隔离开来,但我得到了以下通用结构的失败:

class Foo
@mutex = Mutex.new
....
def self.bar
@mutex.synchronize { ... }
end
end

如果我创建多个调用Foo.bar的线程,有时@mutex会在bar中计算为nil。如果我使用常量(例如MUTEX)而不是实例变量,我就没有这个问题。

我不知道这是否重要,但我正在多核机器上运行JRuby。

如果您能解释或帮助我解决这个问题,我将不胜感激。

更新:我认为这与自动加载有关。使用Rails,我能够在Rails自动加载的一个目录中重现foo.rb的以下内容的类似问题:

class Foo
@mutex = Mutex.new
def self.bar
@mutex.synchronize {}
end
end

当我在Rails控制台中执行以下操作时:

1.upto(4).map { Thread.new { Foo.bar }}.map(&:join)

我得到以下错误:

RuntimeError: Circular dependency detected while autoloading constant Foo
from /Users/palfvin/.rvm/gems/jruby-1.7.10@javlats/gems/activesupport-4.0.1/lib/active_support/dependencies.rb:461:in `load_missing_constant'
from /Users/palfvin/.rvm/gems/jruby-1.7.10@javlats/gems/activesupport-4.0.1/lib/active_support/dependencies.rb:184:in `const_missing'
from (irb):1:in `evaluate'

并且这种行为在CRuby(MRI Ruby)中是相同的。

类变量会发生这种情况吗?@@mutex。在线程之间创建新的类实例可能存在竞争条件,并且@mutex的新副本尚未准备好。然而,常量和类变量在类和子类的副本之间共享。此外,如果您将@mutex初始化代码放在一个存储方法中,例如:

def self.mutex
@mutex ||= Mutex.new
end

虽然自动加载在Rails中确实不像在Ruby 1.9中那样是线程安全的(根据Ruby 1.9中的自动加载线程安全吗

class Foo
@mutex = Mutex.new
def self.bar
@mutex.synchronize { }
end
end
class Foobar < Foo ; end
Foobar.bar

问题是,当从超类执行方法时,self的值保持不变,因此在Foobar对象的上下文中解释Foo.bar中的@mutex的值,而不是Foo对象的值。

这个问题可以通过为互斥对象使用类变量(例如@@mutex)来避免。

最新更新