web-dev-qa-db-fra.com

Qu'entend-on par code "thread-safe"?

Cela signifie-t-il que deux threads ne peuvent pas modifier les données sous-jacentes simultanément? Ou cela signifie-t-il que le segment de code donné fonctionnera avec des résultats prévisibles lorsque plusieurs threads l'exécutent?

334
Varun Mahajan

De Wikipedia:

La sécurité des threads est un concept de programmation informatique applicable dans le contexte de programmes multithreads. Un morceau de code est thread-safe s'il fonctionne correctement lors de l'exécution simultanée par plusieurs threads. En particulier, il doit satisfaire le besoin de plusieurs threads d'accéder aux mêmes données partagées, ainsi que le besoin d'accéder à une donnée partagée par un seul thread à la fois.

Il existe plusieurs façons d’atteindre la sécurité des threads:

Réentrée:

L'écriture de code de telle sorte qu'il puisse être partiellement exécuté par une tâche, réintégrée par une autre tâche, puis reprise à partir de la tâche d'origine. Cela nécessite l'enregistrement des informations d'état dans des variables locales à chaque tâche, généralement sur sa pile, plutôt que dans des variables statiques ou globales.

Exclusion mutuelle:

L'accès aux données partagées est sérialisé à l'aide de mécanismes garantissant qu'un seul thread lise ou écrit les données partagées à tout moment. Il faut faire très attention si un élément de code accède à de multiples éléments de données partagés - les problèmes incluent les conditions de concurrence, des blocages, des blocages de vie, la famine et divers autres maux énumérés dans les manuels de nombreux systèmes d'exploitation.

Stockage local de threads:

Les variables sont localisées de sorte que chaque thread possède sa propre copie privée. Ces variables conservent leurs valeurs dans les limites du sous-programme et des autres codes, et sont sécurisées pour les threads car elles sont locales pour chaque thread, même si le code qui y accède peut être réentrant.

Opérations atomiques:

L'accès aux données partagées s'effectue à l'aide d'opérations atomiques qui ne peuvent pas être interrompues par d'autres threads. Cela nécessite généralement l'utilisation d'instructions spéciales en langage machine, qui peuvent être disponibles dans une bibliothèque d'exécution. Comme les opérations sont atomiques, les données partagées sont toujours conservées dans un état valide, quels que soient les autres threads qui y ont accès. Les opérations atomiques constituent la base de nombreux mécanismes de blocage de fil.

en savoir plus:

http://en.wikipedia.org/wiki/Thread_safety


248
Blauohr

Le code Thread-safe est un code qui fonctionnera même si de nombreux threads l'exécutent simultanément.

http://mindprod.com/jgloss/threadsafe.html

75
Marek Blotny

Une question plus informative est ce qui rend le code non thread thread- et la réponse est qu'il y a quatre conditions qui doivent être vraies ... Imaginez le code suivant (et sa traduction en langage machine)

totalRequests = totalRequests + 1
MOV EAX, [totalRequests]   // load memory for tot Requests into register
INC EAX                    // update register
MOV [totalRequests], EAX   // store updated value back to memory
  1. La première condition est qu'il existe des emplacements de mémoire accessibles à partir de plusieurs threads. En règle générale, ces emplacements sont des variables globales/statiques ou sont accessibles en mémoire de tas à partir de variables globales/statiques. Chaque thread obtient son propre cadre de pile pour les variables locales étendues de fonction/méthode, de sorte que ces variables de fonction/méthode locales, otoh (qui sont sur la pile), sont accessibles uniquement à partir du thread qui possède cette pile.
  2. La deuxième condition est qu’il existe une propriété (souvent appelée un invariant ), associée à ces emplacements de mémoire partagés, qui doit être vraie ou valide. , pour que le programme fonctionne correctement. Dans l'exemple ci-dessus, la propriété est que “ totalRequests doit représenter avec précision le nombre total de fois qu'un thread a exécuté une partie de l'instruction d'incrémentation ”. En règle générale, cette propriété invariante doit conserver la valeur true (dans ce cas, totalRequests doit contenir un nombre précis) avant qu'une mise à jour ait lieu pour que la mise à jour soit correcte.
  3. La troisième condition est que la propriété invariant ne soit PAS maintenue pendant une partie de la mise à jour réelle. (Il est transitoirement invalide ou faux pendant une partie du traitement). Dans ce cas particulier, à partir du moment où totalRequests est récupéré jusqu'au moment où la valeur mise à jour est stockée, totalRequests ne ne satisfait pas l'invariant.
  4. La quatrième et dernière condition qui doit survenir pour qu'une course se produise (et pour que le code soit donc PAS soit " thread-safe ") est qu'un autre thread doit pouvoir accéder à la mémoire partagée alors que l'invariant est cassé, ce qui provoque un comportement incohérent ou incorrect.
48
Charles Bretana

J'aime la définition de Java de Concurrence dans la pratique de Brian Goetz pour son exhaustivité

"Une classe est thread-safe si elle se comporte correctement lorsqu'elle est accédée à partir de plusieurs threads, indépendamment de la planification ou de l'entrelacement de l'exécution de ces threads par l'environnement d'exécution, et sans synchronisation ou autre coordination de la part du code appelant. "

32
Buu Nguyen

Comme d'autres l'ont souligné, la sécurité des threads signifie qu'un morceau de code fonctionnera sans erreur s'il est utilisé par plusieurs threads à la fois.

Il faut savoir que cela a parfois un coût en temps d’ordinateur et en codage plus complexe, ce qui n’est pas toujours souhaitable. Si une classe ne peut être utilisée en toute sécurité que sur un seul thread, il peut être préférable de le faire.

Par exemple, Java a deux classes presque équivalentes, StringBuffer et StringBuilder. La différence est que StringBuffer est thread-safe, de sorte qu'une seule instance de StringBuffer peut être utilisée par plusieurs threads à la fois. StringBuilder n'est pas thread-safe et est conçu pour remplacer de manière plus performante les cas (la grande majorité) lorsque String est construit par un seul thread.

26
Marcus Downing

Thread-safe-code fonctionne comme spécifié, même lorsqu'il est entré simultanément par différents threads. Cela signifie souvent que les structures de données internes ou les opérations devant être exécutées sans interruption sont protégées simultanément contre différentes modifications.

22
Mnementh

Un moyen plus simple de le comprendre est ce qui rend le code non thread-safe. Il y a deux problèmes principaux qui feront qu'une application à threads aura un comportement indésirable.

  • Accéder à une variable partagée sans verrouillage
    Cette variable peut être modifiée par un autre thread lors de l'exécution de la fonction. Vous voulez éviter cela avec un mécanisme de verrouillage pour vous assurer du comportement de votre fonction. La règle générale est de garder le verrou le plus rapidement possible.

  • Blocage causé par la dépendance mutuelle à une variable partagée
    Si vous avez deux variables partagées A et B. Dans une fonction, vous verrouillez A d'abord, puis vous verrouillez B. Dans une autre fonction, vous commencez à verrouiller B et au bout d'un moment, vous verrouillez A. C'est un potentiel impasse où la première fonction attendra que B soit déverrouillé alors que la deuxième fonction attendra que A soit déverrouillée Ce problème ne se produira probablement pas dans votre environnement de développement et uniquement de temps en temps. Pour l'éviter, tous les verrous doivent toujours être dans le même ordre.

20
Hapkido

Oui et non.

La sécurité des threads est un peu plus que de simplement s'assurer que vos données partagées ne sont accessibles que par un thread à la fois. Vous devez assurer un accès séquentiel aux données partagées, tout en évitant conditions de concurrence , blocages , livelocks , et ressource la famine .

Des résultats imprévisibles lorsque plusieurs threads sont en cours d'exécution ne sont pas une condition requise du code thread-safe, mais il s'agit souvent d'un sous-produit. Par exemple, vous pouvez avoir un schéma producteur-consommateur défini avec une file d'attente partagée, un thread producteur et peu de threads consommateurs, et le flux de données peut être parfaitement prévisible. Si vous commencez à attirer plus de consommateurs, vous obtiendrez des résultats plus aléatoires.

9
Bill the Lizard

Simplement - le code fonctionnera correctement si plusieurs threads exécutent ce code en même temps.

8
Greg Balajewicz

En substance, de nombreux problèmes peuvent survenir dans un environnement multithread (réordonnancement d'instructions, objets partiellement construits, même variable ayant des valeurs différentes dans des threads différents en raison de la mise en cache au niveau de la CPU, etc.).

J'aime la définition donnée par Java Concurrency in Practice :

Une [partie de code] est thread-safe si elle se comporte correctement lorsqu'elle est accédée à partir de plusieurs threads, indépendamment de la planification ou de l'entrelacement de l'exécution de ces threads par l'environnement d'exécution, et sans synchronisation ou autre coordination supplémentaire de la part de l'environnement d'exécution. code d'appel.

Par correctement , cela signifie que le programme se comporte conformément à ses spécifications.

Exemple élaboré

Imaginez que vous implémentiez un compteur. Vous pourriez dire qu'il se comporte correctement si:

  • counter.next() ne renvoie jamais une valeur qui a déjà été renvoyée auparavant (nous supposons qu'il n'y a pas de dépassement de capacité, etc. pour des raisons de simplicité)
  • toutes les valeurs comprises entre 0 et la valeur actuelle ont été renvoyées à un moment donné (aucune valeur n'est ignorée)

Un compteur de thread thread safe se comporterait selon ces règles quel que soit le nombre de threads y accédant simultanément (ce qui ne serait généralement pas le cas d’une implémentation naïve).

Note: cross-post sur les programmeurs

8
assylias

J'aimerais ajouter quelques informations supplémentaires à d'autres bonnes réponses.

La sécurité des threads implique que plusieurs threads puissent écrire/lire des données dans le même objet sans erreurs d'incohérence de la mémoire. Dans les programmes hautement multithreads, un programme thread-safe ne provoque pas d'effets secondaires sur les données partagées .

Regardez cette question SE pour plus de détails:

Qu'est-ce que threadsafe signifie?

Un programme thread-safe garantit la cohérence de la mémoire .

De la documentation Oracle page sur l'API simultanée avancée:

Propriétés de cohérence de la mémoire:

Le chapitre 17 de la spécification du langage Java ™ définit la relation passe-avant sur les opérations en mémoire, telles que la lecture et l'écriture de variables partagées. Les résultats d'une écriture par un thread sont visibles pour une lecture par un autre thread uniquement si l'opération d'écriture a lieu avant l'opération de lecture .

Les constructions synchronized et volatile, ainsi que les méthodes Thread.start() et Thread.join(), peuvent former des relations qui se passent avant .

Les méthodes de toutes les classes de Java.util.concurrent et de ses sous-packages étendent ces garanties à la synchronisation de niveau supérieur. En particulier:

  1. Les actions dans un thread avant de placer un objet dans une collection simultanée ont lieu avant des actions postérieures à l'accès ou à la suppression de cet élément de la collection dans un autre thread.
  2. Actions dans un thread avant la soumission d'un Runnable à un Executor happen-avant que son exécution ne commence. De même pour les Callables soumis à un ExecutorService.
  3. Actions entreprises par le calcul asynchrone représenté par un Future actions à réaliser après la récupération du résultat via Future.get() dans un autre thread.
  4. Actions antérieures à la "libération" méthodes de synchroniseur telles que Lock.unlock, Semaphore.release, and CountDownLatch.countDown happen-before actions consécutives à une méthode "d'acquisition" réussie, telle que Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await sur le même synchroniseur objet dans un autre thread.
  5. Pour chaque paire de threads qui échangent avec succès des objets via un Exchanger, les actions antérieures à la exchange() de chaque thread ont lieu avant celles qui ont suivi l'échange () correspondant dans un autre thread.
  6. Les actions préalables à l'appel CyclicBarrier.await et Phaser.awaitAdvance (ainsi que ses variantes) se produisent avant les actions effectuées par l'action barrière et les actions effectuées par l'action barrière se produisent avant des actions consécutives à un retour réussi du correspondant attendent dans d'autres discussions.
5
Ravindra babu

Ne confondez pas la sécurité du fil avec le déterminisme. Le code thread-safe peut également être non déterministe. Compte tenu de la difficulté des problèmes de débogage avec le code threadé, c'est probablement le cas normal. :-)

La sécurité des threads garantit simplement que lorsqu'un thread modifie ou lit des données partagées, aucun autre thread ne peut y accéder de manière à modifier les données. Si votre code dépend d'un certain ordre d'exécution pour être correct, vous avez besoin d'autres mécanismes de synchronisation que ceux requis pour la sécurité des threads.

4
tvanfosson

Pour compléter d'autres réponses:

La synchronisation n'est un souci que lorsque le code de votre méthode effectue l'une des deux choses suivantes:

  1. fonctionne avec des ressources extérieures qui ne sont pas thread-safe.
  2. Lit ou modifie un objet persistant ou un champ de classe

Cela signifie que les variables définies dans votre méthode sont toujours threadsafe. Chaque appel à une méthode a sa propre version de ces variables. Si la méthode est appelée par un autre thread, ou par le même thread, ou même si la méthode s'appelle elle-même (récursivité), les valeurs de ces variables ne sont pas partagées.

La planification des threads n'est pas garantie d'être round-robin. Une tâche peut totalement absorber le processeur aux dépens de threads de même priorité. Vous pouvez utiliser Thread.yield () pour avoir une conscience. Vous pouvez utiliser (en Java) Thread.setPriority (Thread.NORM_PRIORITY-1) pour réduire la priorité d'un thread.

De plus méfiez-vous de:

  • le coût d'exécution élevé (déjà mentionné par d'autres) des applications qui itèrent sur ces structures "thread-safe".
  • Thread.sleep (5000) est censé dormir pendant 5 secondes. Toutefois, si quelqu'un modifie l'heure du système, vous pouvez dormir très longtemps ou ne rien laisser du tout. Le système d'exploitation enregistre l'heure de réveil sous forme absolue et non relative.
3
VonC

Oui et oui. Cela implique que les données ne sont pas modifiées simultanément par plusieurs threads. Cependant, votre programme peut fonctionner comme prévu et sembler thread-safe, même s'il ne l'est pas fondamentalement.

Notez que l'imprévisibilité des résultats est une conséquence des "conditions de concurrence" qui entraînent probablement une modification des données dans un ordre autre que celui attendu.

1
Steve Knight

Répondons à cela par exemple:

class NonThreadSafe {

    private int counter = 0;

    public boolean countTo10() {
        count = count + 1;
        return (count == 10);
    }

La méthode countTo10 ajoute un au compteur, puis renvoie true si le nombre a atteint 10. Elle ne doit renvoyer true qu'une seule fois.

Cela fonctionnera tant qu'un seul thread exécute le code. Si deux threads exécutent le code en même temps, divers problèmes peuvent survenir.

Par exemple, si le nombre commence à 9, un thread peut ajouter 1 (10), mais un deuxième thread peut entrer la méthode et ajouter 1 à nouveau (soit 11) avant que le premier ne puisse exécuter la comparaison avec 10 Ensuite, les deux threads font la comparaison et trouvent ce nombre égal à 11 et aucun des deux ne retourne vrai.

Donc, ce code n'est pas thread-safe.

En substance, tous les problèmes multi-threading sont causés par une variation de ce type de problème.

La solution consiste à s'assurer que l'addition et la comparaison ne peuvent pas être séparées (par exemple en entourant les deux instructions par un code de synchronisation) ou en concevant une solution ne nécessitant pas deux opérations. Un tel code serait thread-safe.

0
rghome

En termes simples: P S'il est sûr d'exécuter plusieurs threads sur un bloc de code, il est thread-safe *

* conditions applicables

Les conditions sont mentionnées par d'autres entités telles que 1. Le résultat doit être identique si vous exécutez un ou plusieurs threads dessus, etc.

0
shabby