28

在Ruby中執行對類變量的寫入/讀取操作不是線程安全的。對實例變量執行寫入/讀取似乎是線程安全的。也就是說,對類或元類對象的實例變量執行寫/讀操作是否線程安全?線程安全:Ruby中的類變量

在線程安全方面,這三個(人爲的)例子之間有什麼區別?

實施例1:互斥

class BestUser # (singleton class) 
    @@instance_lock = Mutex.new 

    # Memoize instance 
    def self.instance 
    @@instance_lock.synchronize do 
     @@instance ||= best 
    end 
    end 
end 

實施例2:INSTANCE變量存儲

class BestUser # (singleton class) 
    # Memoize instance 
    def self.instance 
    @instance ||= best 
    end 
end 

實施例3:INSTANCE變量存儲ON元類

class BestUser # (singleton class) 
    # Memoize instance 
    class << self 
    def instance 
     @instance ||= best 
    end 
    end 
end 

回答

21

實施例2和3完全相同。模塊和類也是對象,在一個對象上定義一個單例方法實際上在它的單例類中定義它。

這樣說,既然你已經建立了實例變量訪問是線程安全的,例子2和3是線程安全的。示例1也應該是線程安全的,但比其他兩個要差,因爲它需要手動變量同步。但是,如果您需要利用繼承樹內共享類變量的事實,則可能必須使用第一種方法。


Ruby語言的固有線程安全性取決於實現。

MRI之前,1.9,實施線程at the VM level。這意味着,儘管Ruby能夠調度代碼執行,但在單個Ruby進程中沒有任何並行運行並行。 Ruby 1.9使用與global interpreter lock同步的本地線程。只有持有鎖的上下文可以執行代碼。

n, x = 10, 0 

n.times do 
    Thread.new do 
    n.times do 
     x += 1 
    end 
    end 
end 

sleep 1 
puts x 
# 100 

x值是始終 MRI上是一致的。然而,在JRuby上,圖片發生了變化。相同算法的多次執行產生了值76,87,98,88,94。結果可能是任何事情,因爲JRuby使用Java線程,它們是真正的線程並行執行。

就像在Java語言中一樣,爲了安全地使用JRuby中的線程,需要手動同步。以下代碼始終產生一致的值x

require 'thread' 
n, x, mutex = 10, 0, Mutex.new 

n.times do 
    Thread.new do 
    n.times do 
     mutex.synchronize do 
     x += 1 
     end 
    end 
    end 
end 

sleep 1 
puts x 
# 100 
+2

如果訪問實例變量實際上是線程安全的,或者僅僅基於我認爲它*看起來是*的假設,您知道副手嗎? – 2012-03-05 14:27:28

+0

@AnomalousThought,查看有關線程安全性的一些信息的更新答案。 – 2012-03-05 17:24:07

+0

@MatheusMoreira當你有機會時,你介意看看http://stackoverflow.com/questions/21735401/using-class-instance-variable-for-mutex-in-ruby嗎?謝謝。 – 2014-02-12 20:49:37

5

示例2和3完全相同。他們根本不是線程安全的。

請參閱下面的示例。

class Foo 
    def self.bar 
    @bar ||= create_no 
    end 

    def self.create_no 
    no = rand(10000) 
    sleep 1 
    no 
    end 
end 

10.times.map do 
    Thread.new do 
    puts "bar is #{Foo.bar}" 
    end 
end.each(&:join) 

它的結果是不一樣的。 使用互斥體時的結果如下。

class Foo 
    @mutex = Mutex.new 

    def self.bar 
    @mutex.synchronize { 
     @bar ||= create_no 
    } 
    end 

    def self.create_no 
    no = rand(10000) 
    sleep 1 
    no 
    end 
end 

10.times.map do 
    Thread.new do 
    puts "bar is #{Foo.bar}" 
    end 
end.each(&:join) 

它在CRuby 2.3.0上運行。

+0

我不知道我明白。當然,線程安全的結果總是不同的,因爲每個線程都可以爲'@ bar'設置自己的值。如果你用@@ bar替換'@ bar',你總會得到相同的結果。基於這個假設,你是說'@@ bar'是線程安全的嗎? – Magnuss 2017-03-28 06:55:38

+1

@bar是Foo類的實例變量。它不是每個線程所擁有的。它由所有線程共享。 – 2017-05-12 06:15:44

+1

實際上,線程安全意味着最頂層的例子中的結果應該不同(即其他線程不會插入實例變量),就像@Magnuss所指出的那樣。所以你的例子似乎證明了OP的問題中的例子2和3是線程安全的。 – Magne 2017-11-21 13:10:29

2

Instance variables are not thread safe(和類變量甚至更線程安全)

實施例2和3中,與實例變量,是等效的,並且它們NOT線程安全,像@VincentXie說明。但是,這裏有一個更好的例子來證明爲什麼他們都沒有:

class Foo 
    def self.bar(message) 
    @bar ||= message 
    end 
end 

t1 = Thread.new do 
    puts "bar is #{Foo.bar('thread1')}" 
end 

t2 = Thread.new do 
    puts "bar is #{Foo.bar('thread2')}" 
end 

sleep 2 

t1.join 
t2.join 

=> bar is thread1 
=> bar is thread1 

因爲實例變量被所有線程共享的,就像@VincentXie在他的評論說。

PS:實例變量有時稱爲「類的實例變量」,這取決於在其中使用它們的上下文:

當自被一個類,它們是類的實例變量(類 實例變量)。當自己是一個對象時,它們是對象變量(實例變量)的實例 。 - WindorC's answer to a question about this