Ruby 中的线程局部实例局部变量

Thread-local instance-local variable in Ruby

我想要一个本地线程(在 Rails 应用程序的上下文中,每个请求),实例本地 var,举例来说,假设我有一个可全局访问的对象实例。

module Event
  def self.bus
    @bus ||= Bus.new
  end
end

bus 对象可以根据请求设置元数据:

class Bus
  def with_metadata(metadata, &block)
    @metadata = metadata
    block.call
  end

  # ...
end

问题在于,在线程环境中,例如 Puma 上的 Rails 应用程序,@metadata 在不同线程(即请求)之间全局共享,因此如果元数据包含 IP 地址,例如,它只会对某些请求不正确。

我们可以使用线程本地的 Thread.current

class Bus
  def with_metadata(metadata, &block)
    Thread.current["with_metadata"] = metadata
    block.call
  ensure
    Thread.current["with_metadata"] = nil
  end

  # ...
end

但是这会引入一个问题,如果总线对象的另一个实例在同一线程中初始化,即 Event.busBus.new,它们会错误地共享“with_metadata”状态.

我的想法是在密钥中包含 object_id

class Bus
  def with_metadata(metadata, &block)
    Thread.current["#{object_id}_with_metadata"] = metadata
    block.call
  ensure
    Thread.current["#{object_id}_with_metadata"] = nil
  end

  # ...
end

这听起来合理吗,是否有一种惯用的方法来拥有线程局部变量、实例局部变量?我不想依赖 Rails 或任何其他库。

Rails 依赖于 concurrent-ruby gem,它为您带来了很多处理 multi-threaded 和并发环境的工具。

具体可以在这里使用Concurrent::ThreadLocalVarclass:

class Bus
  def initialize
    @metadata = Concurrent::ThreadLocalVar.new
  end

  def metadata
    @metadata.value
  end

  def with_metadata(metadata, &block)
    @metadata.value = metadata
    yield
  ensure
    @metadata.value = nil
  end

  # ...
end

请参阅上面链接的文档以获取更多使用示例。