Aucun des structures de données de base sont thread-safe. Le seul que je connais qui est livré avec le Rubis est la file d'attente de la mise en œuvre de la bibliothèque standard (require 'thread'; q = Queue.new
).
IRM GIL ne nous sauve pas de thread questions de sécurité. Il permet seulement de garantir que deux threads ne peuvent pas exécuter de code Ruby dans le même temps, c'est à dire sur deux Processeurs différents en même temps exact. Les Threads peuvent encore être interrompu et repris à tout moment dans votre code. Si vous écrivez du code, comme @n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
par exemple, la mutation d'une variable partagée à partir de plusieurs threads, la valeur de la variable partagée par la suite n'est pas déterministe. Le GIL est plus ou moins un exercice de simulation d'un système de base unique, il ne change pas les problèmes fondamentaux de l'écriture correcte simultanée de programmes.
Même si l'IRM a été monothread comme Node.js vous auriez encore à penser la simultanéité. L'exemple de la variable incrémentée va fonctionner très bien, mais vous pouvez toujours obtenir des conditions de course où les choses se passent en non-déterministe et un rappel clobbers la suite de l'autre. Mono-thread asynchrone systèmes sont plus faciles à comprendre, mais ils ne sont pas exempts de problèmes de concurrence. Il suffit de penser d'une application avec plusieurs utilisateurs: si deux utilisateurs, appuyez sur modifier sur un Débordement de la Pile de post, plus ou moins en même temps, de passer quelques temps à l'édition de la poste, puis cliquez sur "enregistrer", dont les modifications seront vus par un tiers utilisateur, plus tard, quand ils ont lu le même post?
En Ruby, comme dans la plupart des autres exécutions simultanées, tout ce qui est plus qu'une opération n'est pas thread-safe. @n += 1
n'est pas thread-safe, parce qu'il est de multiples opérations. @n = 1
est thread-safe parce que c'est une opération, c'est beaucoup d'opérations sous le capot, et je serais probablement avoir des ennuis si j'ai essayé d'expliquer pourquoi c'est "thread-safe" dans le détail, mais à la fin vous ne serez pas obtenir des résultats incohérents de devoirs). @n ||= 1
, n'est pas, et aucun autre abréviation de l'opération + de cession est soit. Une erreur que j'ai fait de nombreuses fois l'écriture est - return unless @started; @started = true
, ce qui n'est pas thread-safe.
Je ne connais pas de liste officielle de thread-safe et non thread-safe consolidés pour Ruby, mais il y a une règle simple: si une expression ne fait qu'une seule (sans effets secondaires) fonctionnement, il est probablement thread-safe. Par exemple: a + b
est ok, a = b
est également ok, et a.foo(b)
est ok, si la méthode foo
est sans effets secondaires (depuis à peu près tout en Ruby, c'est un appel de méthode, même affectation dans de nombreux cas, cela vaut pour les autres exemples). Effets secondaires dans ce contexte, signifie les choses qui changent d'état. def foo(x); @x = x; end
est pas sans effets secondaires.
L'une des choses les plus difficiles au sujet de l'écriture "thread-safe" code en Ruby, c'est que toutes les structures de données de base, y compris le tableau, de hachage et de la ficelle, sont mutables. Il est très facile de accidentellement fuite d'une pièce de votre état, et lorsque cette pièce est mutable choses peuvent devenir vraiment foiré. Considérons le code suivant:
class Thing
attr_reader :stuff
def initialize(initial_stuff)
@stuff = initial_stuff
@state_lock = Mutex.new
end
def add(item)
@state_lock.synchronize do
@stuff << item
end
end
end
Une instance de cette classe peuvent être partagées entre les threads et ils peuvent ajouter des choses, mais il y a une simultanéité bug (ce n'est pas la seule): l'état interne de l'objet de fuites à travers l' stuff
accesseur. En plus d'être problématique à partir de l'encapsulation point de vue, il ouvre aussi un peut de la simultanéité des vers. Peut-être que quelqu'un prend ce tableau et les transmet à un autre endroit, et que le code à son tour, pense qu'il est maintenant propriétaire de ce tableau et peut faire ce qu'il veut avec elle.
Un autre classique de Ruby exemple est la suivante:
STANDARD_OPTIONS = {:color => 'red', :count => 10}
def find_stuff
@some_service.load_things('stuff', STANDARD_OPTIONS)
end
find_stuff
fonctionne très bien la première fois qu'il l'utilise, mais renvoie autre chose une deuxième fois. Pourquoi? L' load_things
méthode arrive à penser qu'il possède les options de hachage est passé, et n' color = options.delete(:color)
. Maintenant l' STANDARD_OPTIONS
de la constante n'a pas la même aura plus de valeur. Les constantes sont la seule constante dans ce qu'ils font référence, ils ne garantissent pas la constance des structures de données, ils se réfèrent. Imaginez ce qui arriverait si ce code a été exécuté en même temps.
Si vous évitez le partage de la mutable état (par exemple, les variables d'instance dans les objets accessibles par plusieurs threads, des structures de données comme les hachages et les tableaux accessibles par plusieurs threads) la sécurité des threads n'est pas si dur. Essayez de minimiser les parties de votre application, qui sont accessibles simultanément, et de concentrer vos efforts. IIRC, dans une application Rails, un nouveau contrôleur de l'objet est créée pour chaque demande, il va seulement pour obtenir utilisé que par un seul thread, et il en va de même pour n'importe quel modèle les objets que vous créez à partir de ce contrôleur. Cependant, les Rails encourage également l'utilisation de variables globales (User.find(...)
utilise la variable globale User
, vous pensez peut-être seulement une classe, et c'est une classe, mais c'est aussi un espace de noms pour les variables globales), certains de ces derniers sont sûrs parce qu'ils sont en lecture seule, mais parfois vous sauver de choses dans ces variables globales, parce que c'est pratique. Soyez très prudent lorsque vous utilisez tout ce qui est accessible dans le monde entier.
Il a été possible d'exécuter des Rails, dans les environnements multithread pour un bon moment maintenant, donc, sans être un Rails expert je voudrais encore aller aussi loin que de dire que vous n'avez pas à vous soucier de la sécurité des threads quand il s'agit de Rails de lui-même. Vous pouvez créer des applications Rails qui ne sont pas thread-safe par faire certaines des choses que je mentionne ci-dessus. Quand il s'agit d'autres pierres précieuses supposer qu'ils ne sont pas thread-safe, sauf si ils disent qu'ils sont, et s'ils disent qu'ils sont supposer qu'ils ne le sont pas, et de regarder à travers leur code (mais juste parce que vous voyez qu'ils vont les choses comme @n ||= 1
ne signifie pas qu'ils ne sont pas thread-safe, qui est parfaitement légitime de le faire dans le bon contexte -- vous devriez plutôt chercher des choses comme mutable état dans des variables globales, comment il gère mutable objets passés à ses méthodes, et surtout comment il gère les options de hachages).
Enfin, le fil dangereuse est une propriété transitive. Tout ce qui utilise quelque chose qui n'est pas thread-safe lui, n'est pas thread-safe.