94 votes

Comment savoir ce qui n'est PAS thread-safe en rubis?

à partir de Rails 4, tout devrait fonctionner dans filetée de l'environnement par défaut. Ce que cela signifie, c'est tout le code que nous écrivons ET _TOUS_ les pierres que nous utilisons sont nécessaires pour être threadsafe

donc, j'ai quelques questions à ce sujet:

  1. ce n'est PAS thread-safe en ruby/rails? Vs Ce qui est thread-safe en ruby/rails?
  2. S'il existe une liste de pierres précieuses qui est connu pour être thread-safe ou vice-versa?
  3. est-il de la Liste de modèles communs de code qui ne sont PAS thread-safe exemple @result ||= some_method?
  4. Sont les structures de données en ruby lang de base tels que l' Hash etc des threads?
  5. Sur l'IRM, où il y a un GVL/GIL ce qui signifie que seulement 1 ruby thread peut s'exécuter à la fois, sauf pour IO, le changement de thread-safe effet nous?

113voto

Theo Points 60103

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.

10voto

crizCraig Points 2041

En plus de la réponse de Theo, j'aimerais ajouter quelques problèmes à rechercher dans Rails, si vous passez à config.threadsafe!

  • Variables de classe :

    @@i_exist_across_threads

  • ENV :

    ENV['DONT_CHANGE_ME']

  • Fils :

    Thread.start

9voto

dre-hh Points 1858

à partir de Rails 4, tout devrait fonctionner dans filetée de l'environnement par défaut

Ce n'est pas 100% correct. Des threads Rails est juste sur par défaut. Si vous avez encore le déployer sur un multi-processus de serveur d'applications comme passager(communauté) ou de la licorne n'y aura aucune différence du tout. Ce changement ne concerne que vous, si vous effectuez un déploiement sur un environnement multi-thread comme Puma ou Passager d'Entreprise > 4.0

Dans le passé, si vous souhaitez déployer sur un multi-thread serveur d'application vous avez eu à tourner sur config.thread-safe, qui est par défaut, car toutes n'avaient pas d'effets ou également appliquée à une application rails, s'exécute dans un processus unique (Prooflink).

Mais si vous ne voulez tous les rails 4 streaming avantages sociaux et les autres en temps réel des trucs du multithread de déploiement alors peut-être que vous trouverez cet article intéressant. @Theo triste, pour une application rails, vous actauly suffit d'omettre la mutation de l'état statique lors d'une requête. Bien que cette une pratique simple à suivre, unfortionatly vous ne pouvez pas être sûr de ce que pour chaque bijou vous trouvez. Aussi loin que je me souviens de Charles Oliver Nutter de la Jruby projet a eu des trucs à ce sujet dans ce podcast.

Et si vous voulez écrire un pur simultanées de programmation ruby, où vous auriez besoin de quelques structures de données qui sont accessibles par plus d'un thread peut-être vous trouverez le thread_safe gem utile

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X