Ruby Rails中的背景线程可以';看不到实例变量

Ruby Rails中的背景线程可以';看不到实例变量,ruby,ruby-on-rails-3,Ruby,Ruby On Rails 3,我需要从rails应用程序收集一些数据,对其进行聚合,并定期将其发送到远程服务器。我在application.rb中的全局变量(我知道,我知道)中实例化聚合类 在我的聚合类中,我启动一个休眠10秒的线程,然后查看队列,处理数据并发送它。队列是存储在类的实例变量中的哈希 在rails控制器中,我调用aggregator类中的一个方法来对散列中的数据进行排队。当然,这与读取队列的后台任务在不同的线程上。问题是后台任务从未在散列中看到任何数据。在我的日志中,我在写入(从controllers线程)和读

我需要从rails应用程序收集一些数据,对其进行聚合,并定期将其发送到远程服务器。我在application.rb中的全局变量(我知道,我知道)中实例化聚合类

在我的聚合类中,我启动一个休眠10秒的线程,然后查看队列,处理数据并发送它。队列是存储在类的实例变量中的哈希

在rails控制器中,我调用aggregator类中的一个方法来对散列中的数据进行排队。当然,这与读取队列的后台任务在不同的线程上。问题是后台任务从未在散列中看到任何数据。在我的日志中,我在写入(从controllers线程)和读取(从后台线程)时都打印出散列的object_id。hash#object_id与两个线程匹配,但后台线程从未看到数据

最让我头疼的是这在rails之外工作得很好。我已经用许多线程设置了测试,这些线程确实对它产生了影响,并且工作得很好(为了清晰起见,我没有展示一些线程保护)。有人知道
对象id
如何匹配,但内容不一致吗

class Aggregator

def initialize
  @q = {}
  @timer = nil
end

def start
  @timer = Thread.new do
    loop do
      sleep(10)
      flush_q
    end
  end
end

def flush_q
  logger.debug "flush: q.object_id = #{@q.object_id}"  # matches what I get below
  logger.debug "flush: q.length = #{@q.length}"   # always zero!
  @q.each_pair do |k,v|
    # pack it up and send it
  end
  @q.clear
end

def add(item)
  logger.debug "add: q.object_id = #{@q.object_id}"  # matches what I get above
  @q[item.name] ||= item
  logger.debug "add: q.length = #{@q.length}"   # increases with each add
  # not actually that simple, but not relevant
end

end

我将冒险假设您的代码是使用分叉应用服务器(例如unicorn或passenger)部署的

这意味着您的应用程序加载一次,然后从主实例派生新实例。分叉很便宜,因此这意味着应用程序的新实例可以很快启动/关闭

我相信您的聚合器实例正在这个主进程中创建/启动。当这分叉时,复制进程的整个内存空间(因此新进程中有一个aggregator实例,具有相同的对象id等等)

但是,在分叉时,只复制当前线程,因此聚合器刷新只在主进程中发生,而所有追加都在子进程中发生。您可以通过在日志中添加
process.pid
来确认这一点-您应该看到您的日志来自两个不同的进程

解决此问题的一种方法是在子进程分叉后启动/重新启动线程。你如何做到这一点取决于应用程序的服务方式。使用unicorn,您可以通过
after\u fork
方法在unicorn配置中执行此操作。有乘客吗

PhusionPassenger.on_event(:starting_worker_process) do |forked|
  if forked
    ...
  end
end

您如何调用
start
add
?“start”在我创建该类的单个实例后被调用一次。”add从rails控制器调用。我注意到的第一件事是,您没有任何互斥同步,这意味着对哈希的访问不受线程控制,这可能会有问题。聚合器实例在哪里定义?是否正在生产中?如果是这样的话,应用程序是如何部署的?@chrisheald:关于线程安全,我会注意到这一点,但为了清楚起见,我把它遗漏了。在我的测试中,我在没有任何负载的情况下运行它,它总是失败。关于这个实例,我创建它并在
application.rb
中启动它。我把它保存在一个全局变量中,这样所有控制器都可以使用它。更好的主意?我怀疑是这样的。即使是
对象id
s也将是相同的?明天早上我会马上检查。因为整个内存空间都被复制了,所以一切都会一样。唯一不能复制的是线程,就是这样。被认为对象ID是内存地址的合适替代品所愚弄。没有意识到他们会在叉子后保持不变。谢谢你,弗雷德!它是内存地址的合适代表,但是这些地址是进程的内存空间,所以它们也会被复制。所以我重新启动后台线程以响应fork,它有时会工作。但有时进程似乎在后台循环开始读取缓冲区之前就被终止了。