28

Realizar escrituras/lecturas en variables de clase en Ruby no es seguro para subprocesos. La realización de escrituras/lecturas en variables de instancia parece ser segura para subprocesos. Dicho esto, ¿es seguro realizar lecturas/lecturas en variables de instancia de una clase o un objeto de metaclase?Seguridad de subprocesos: Variables de clase en Ruby

¿Cuáles son las diferencias entre estos tres ejemplos (artificiales) en términos de seguridad de subprocesos?

Ejemplo 1:exclusión mutua

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

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

Ejemplo 2:almacenamiento de instancia VARIABLE

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

Ejemplo 3:almacenamiento de instancia VARIABLE EN MetaClass

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

Respuesta

21

Ejemplos 2 y 3 son exactamente lo mismo. Los módulos y las clases también son objetos, y la definición de un método singleton en un objeto realmente lo define en su clase singleton.

Dicho esto, y como ya ha establecido que el acceso variable de la instancia es seguro para la ejecución de subprocesos, los ejemplos 2 y 3 son seguros para la ejecución de subprocesos. El ejemplo 1 también debe ser seguro para subprocesos, pero es inferior a los otros dos porque requiere sincronización de variable manual.

Sin embargo, si necesita aprovechar el hecho de que las variables de clase se comparten en el árbol de herencia, es posible que deba usar el primer enfoque.


La seguridad del hilo inherente del lenguaje Ruby depende de la implementación.

MRI, antes 1.9, hilos implementados at the VM level. Esto significa que, aunque Ruby es capaz de programar la ejecución del código, en realidad nada se está ejecutando en paralelo dentro de un único proceso de Ruby. Ruby 1.9 usa subprocesos nativos sincronizados con un global interpreter lock. Solo el contexto que contiene el bloqueo puede ejecutar el código.

n, x = 10, 0 

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

sleep 1 
puts x 
# 100 

El valor de x es siempre consistente en la RM. En JRuby, sin embargo, la imagen cambia. Las ejecuciones múltiples del mismo algoritmo arrojaron los valores 76, 87, 98, 88, 94. El resultado podría ser cualquier cosa porque JRuby usa hilos de Java, que son hilos reales y se ejecutan en paralelo.

Al igual que en el lenguaje Java, se requiere sincronización manual para utilizar hilos de manera segura en JRuby. El siguiente código siempre resulta en valores coherentes para 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

¿Sabes si el acceso a las variables de instancia es realmente seguro o es simplemente basado en mi suposición de que * parece * ser? –

+0

@AnomalousThought; consulte la respuesta actualizada para obtener información acerca de la seguridad de las secuencias. –

+0

@MatheusMoreira ¿Te importaría echarle un vistazo a http://stackoverflow.com/questions/21735401/using-class-instance-variable-for-mutex-in-ruby cuando tienes la oportunidad? Gracias. –

5

los Ejemplos 2 y 3 son exactamente lo mismo. No son en absoluto hilos de seguridad.

Consulte el ejemplo a continuación.

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) 

Su resultado no es el mismo. El resultado es el mismo cuando se utiliza mutex como a continuación.

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) 

Se ejecuta en CRuby 2.3.0.

+0

No estoy seguro de entender. Por supuesto, el resultado siempre será diferente en una forma segura de subprocesos, ya que cada subproceso puede establecer su propio valor para '@ bar'. Si reemplaza '@ bar' con' @@ bar', siempre obtendrá el mismo resultado. Basado en esa suposición, ¿estás diciendo '@@ bar' es seguro para subprocesos? – Magnuss

+1

@bar es una variable de instancia de la clase Foo. No es propiedad de cada hilo. Es compartido por todo el hilo. –

+1

En realidad, la seguridad de la hebra significa que los resultados en el ejemplo superior _deberían ser diferentes (es decir, otros hilos no interferirán con la variable de instancia), como @Magnuss indicado.Por lo tanto, su ejemplo parece demostrar que los ejemplos 2 y 3 en la pregunta del OP son 'a prueba de hilos'. – Magne

2

Instance variables are not thread safe (y variables de clase son incluso menos seguro de rosca)

Ejemplo 2 y 3, tanto con las variables de instancia, son equivalentes, y son NO hilo seguro, como @VincentXie declaró. Sin embargo, aquí es un mejor ejemplo para demostrar por qué no son:

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 

Debido a que la variable de instancia es compartida entre todos los hilos, como @VincentXie declaró en su comentario.

PS: Las variables de instancia se refieren a veces como "variables de instancia de clase", dependiendo del contexto en el que se utilizan:

Cuando el yo es una clase, que son variables de instancia de clases (clase variables de instancia). Cuando self es un objeto, son instancias variables de objetos (variables de instancia). - WindorC's answer to a question about this

Cuestiones relacionadas