2013-07-17 31 views
2

我需要從Rails應用程序收集一些數據,將其聚合並定期將其發送到遠程服務器。我在application.rb中實例化了一個全局變量中的聚合類(我知道,我知道)。Rails中的後臺線程無法看到實例變量

在我的聚合類中,我啓動了睡眠10秒的線程,然後查看隊列,處理數據併發送它。隊列是存儲在類的實例變量中的哈希。

從rails控制器中,我調用aggregator類中的一個方法將數據排列在散列中。當然,這與讀取隊列的後臺任務不同。問題是後臺任務從未看到散列中的任何數據。在我的日誌中,我寫出哈希的object_id(從控制器線程寫入),以及從背景線程讀取哈希時的object_id。 hash#object_id匹配兩個線程,但後臺線程從不會看到數據。

什麼查殺我就是這個工作正常軌道外。我已經建立了許多線程的測試,真正的重擊它,它工作正常(有一些線程保護,我沒有顯示爲清晰)。任何人都知道object_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 
+0

你是怎麼調用'start'和'add'? –

+0

'start'在創建此類的單個實例後被調用一次。 'add'從軌道控制器被調用。 – Daiku

+0

嗯,我注意到的第一件事是你沒有任何互斥體同步,這意味着對線程的訪問不受線程控制,這可能是有問題的。在哪裏定義了聚合器實例? –

回答

0

我打算冒險,假設你的代碼是使用分派應用服務器(例如獨角獸或乘客)部署的。

這意味着您的應用程序會加載一次,然後從該主實例派生出新實例。分叉很便宜,所以這意味着應用程序的新實例可以很快啓動/關閉。

我相信你的聚合器實例正在這個主進程中創建/啓動。當這分叉進程的整個內存空間被複制時(所以在新進程中有一個聚合器實例,具有相同的對象ID等等)。

然而,當分支僅複製當前線程時,聚集器刷新只發生在主進程中,但所有附加操作都發生在子進程中。您可以通過將Proccess.pid添加到您的日誌中來確認這一點 - 您應該看到您的日誌記錄來自2個不同的進程。

修復此問題的一種方法是在子進程分叉後啓動/重新啓動線程。你如何做到這一點取決於應用程序的服務方式。使用獨角獸,您可以通過after_fork方法在獨角獸配置中執行此操作。隨着你的乘客

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

我懷疑是這樣的。即使'object_id's將是相同的?我會在早上馬上檢查。 – Daiku

+0

因爲整個內存空間都被複制,所有內容都是一樣的。唯一不能複製的是線程。 –

+0

就是這樣。通過假設對象ID是內存地址的合適替代品而被愚弄了。沒有意識到他們會在分叉之後保持不變。謝謝,弗雷德! – Daiku