28

Wykonywanie zapisów/odczytów zmiennych klas w Ruby nie jest bezpieczne dla wątków. Wykonywanie zapisów/odczytów zmiennych instancji wydaje się być bezpieczne dla wątków. Powiedział, że jest wątek bezpieczny do wykonywania zapisu/odczytu na zmiennych instancji obiektu klasy lub metaclass?Bezpieczeństwo wątków: zmienne klas w języku Ruby

Jakie są różnice między tymi trzema (wymyślonymi) przykładami pod względem bezpieczeństwa gwintów?

Przykład 1:wzajemnego wykluczania

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

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

Przykład 2:zmiennej instancji magazynowanie

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

Przykład 3:zmiennej instancji Składowanie na metaklasą

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

Odpowiedz

21

Przykłady 2 i 3 są dokładnie takie same. Moduły i klasy są również obiektami, a zdefiniowanie metody singleton na obiekcie faktycznie definiuje ją na swojej klasie singleton.

Po tym, jak już wspomniano, ponieważ dostęp do zmiennej instancji jest bezpieczny dla wątków, przykłady 2 i 3 są bezpieczne dla wątków. Przykład 1 powinien być bezpieczny dla wątków, ale jest gorszy od pozostałych dwóch, ponieważ wymaga ręcznej, zmiennej synchronizacji.

Jeśli jednak chcesz skorzystać z faktu, że zmienne klasy są udostępniane w drzewie dziedziczenia, konieczne może być zastosowanie pierwszego podejścia.


Własne bezpieczeństwo wątków języka Ruby zależy od implementacji.

MRI, przed 1.9, zaimplementowano wątki at the VM level. Oznacza to, że nawet jeśli Ruby jest w stanie zaplanować wykonywanie kodu, nic nie jest tak naprawdę uruchomione równolegle w jednym procesie Ruby. Ruby 1.9 używa natywnych wątków zsynchronizowanych z global interpreter lock. Tylko kontekst, w którym znajduje się blokada, może wykonywać kod.

n, x = 10, 0 

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

sleep 1 
puts x 
# 100 

Wartość x jest zawsze spójne na MRI. Jednak w JRuby obraz się zmienia. Wielokrotne wykonanie tego samego algorytmu przyniosło wartości: 76, 87, 98, 3. Rezultatem może być cokolwiek, ponieważ JRuby używa wątków Java, które są rzeczywistymi wątkami i wykonują równolegle.

Podobnie jak w języku Java, wymagana jest synchronizacja ręczna, aby bezpiecznie używać wątków w JRuby. Poniższy kod powoduje zawsze spójne wartości dla 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

Czy znasz się na sobie, jeśli dostęp do zmiennych instancji jest w rzeczywistości bezpieczny dla wątków, czy jest to oparte na moim założeniu, że * wydaje się być? –

+0

@AnomalousThought, zobacz zaktualizowaną odpowiedź, aby uzyskać informacje na temat bezpieczeństwa wątków. –

+0

@MatheusMoreira Czy mógłbyś rzucić okiem na http://stackoverflow.com/questions/21735401/using-class-instance-variable-for-mutex-in-ruby, gdy masz szansę? Dzięki. –

5

Przykłady 2 i 3 są dokładnie takie same. W ogóle nie są zabezpieczeniem gwintów.

Proszę zobaczyć przykład poniżej.

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) 

Jego wynik nie jest taki sam. Wynik jest taki sam, gdy używasz muteksu, jak poniżej.

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) 

Jest uruchamiany na CRuby 2.3.0.

+0

Nie jestem pewien, czy rozumiem. Oczywiście wynik zawsze będzie inny w sposób bezpieczny dla wątków, ponieważ każdy wątek jest w stanie ustawić własną wartość '@ bar'. Jeśli zastąpisz '@ bar' przez' @@ bar', zawsze uzyskasz taki sam wynik. W oparciu o to założenie, czy mówisz "@@ bar" jest bezpieczny dla wątków? – Magnuss

+1

@bar jest zmienną instancji klasy Foo. Nie jest własnością każdego wątku. Jest dzielony przez wszystkie wątki. –

+1

W rzeczywistości, bezpieczeństwo wątków oznaczałoby, że wyniki w twoim najwyższym przykładzie mogą być inne (tj. Inne wątki nie będą mieszać się ze zmienną instancji), jak wskazano @Magnuss.Twój przykład zdaje się pokazywać, że przykład 2 i 3 w pytaniu OP _are_ jest bezpieczny dla wątków. – Magne

2

Instance variables are not thread safe (zmienne grupy są jeszcze mniej bezpieczne gwintu)

Przykład 2 i 3, zarówno ze zmiennymi przykład, są równoważne i mają nie gwint bezpieczne, jak @VincentXie inaczej. Jednak tutaj jest lepszym przykładem, aby wykazać, dlaczego nie są one:

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 

Ponieważ zmienna instancji jest dzielone pomiędzy wszystkich wątków, jak @VincentXie stwierdził w swoim komentarzu.

PS: Zmienne instancji są czasami określane jako „zmienne instancji klasy”, w zależności od kontekstu, w jakim są one wykorzystywane:

Kiedy jaźń jest klasa, są zmienne instancji klas (klasa zmienne instancji). Kiedy self jest obiektem, są instancjami zmiennymi obiektów (zmiennymi instancji). - WindorC's answer to a question about this

Powiązane problemy