2014-03-26 98 views
0

我很好奇Ruby中哈希的線程安全性。運行從控制檯以下(紅寶石2.0.0-P247):Ruby中哈希的線程安全

h = {} 
10.times { Thread.start { 100000.times {h[0] ||= 0; h[0] += 1;} } } 

回報

{0=>1000000} 

這是正確的預期值。

它爲什麼有效?我能否依賴這個版本的Ruby來保證線程安全?

編輯:測試100次:

counter = 0 
100.times do 
    h={} 
    threads = Array.new(10) { Thread.new { 10000.times { h[0] ||= 0; h[0] += 1 } } } 
    threads.map { |thread| thread.join } 
    counter += 1 if h[0] != 100000 
end 
puts counter 

計數器仍是0結尾。我嘗試了10K次,並且從來沒有出現過這個代碼的單個線程安全問題。

回答

2

不,你不能依賴哈希線程安全,因爲它們不是線程安全的,最可能是出於性能原因。爲了克服標準庫的這些限制,已經創建了提供線程安全(thread_safe)或不可變(倉鼠)數據結構的Gems。這些將使訪問數據線程安全,但您的代碼除此之外還有一個不同的問題:

您的輸出不會是確定性的;事實上,我試過你編碼幾次,結果我得到了544988。在你的代碼中,經典的race condition可能會出現,因爲有獨立的讀寫步驟(即它們不是原子的)。考慮表達式h[0] ||= 0which basically translates to h[0] || h[0] = 0。現在,很容易在那裏的競爭條件發生來構造的情況下:

  • 線程1讀取h[0]並發現它是nil
  • 線程2讀取h[0]並發現它是nil
  • 線程1套h[0] = 0和增量h[0] += 1
  • 螺紋2套h[0] = 0和增量h[0] += 1
  • 所得到的散列是{0=>1}雖然正確的結果將是{0=>2}

如果你想確保你的數據不會被破壞,你可以鎖定操作with a mutex

require 'thread' 
semaphore = Mutex.new 

h = {} 

10.times do 
    Thread.start do 
    semaphore.synchronize do 
     100000.times {h[0] ||= 0; h[0] += 1;} 
    end 
    end 
end 
+0

另一種解決方案是一成不變的數據結構。 https://github.com/hamstergem/hamster – Reactormonk

+0

@Reactormonk我很確定線程安全的數據結構不會處理競爭條件的問題。通過引用thread_safe gem引起了同樣的錯誤;-)這些gem只提供對數據結構的線程安全訪問,但是這裏的問題出現在與數據結構無關的單獨的讀/寫步驟中。 –

+0

謝謝!我知道這些類型的代碼可能會出現這些寶石和線程安全問題。我用樣本實驗編輯了我的問題,而不是一次就能重現代碼中的線程安全問題。我很好奇這個代碼爲什麼沒有被破解,如果這只是一個例外,或者它總是以這種方式工作。 –