Que signifie threadsafe?


124

Récemment, j'ai essayé d'accéder à une zone de texte à partir d'un fil (autre que le fil d'interface utilisateur) et une exception a été levée. Il a dit quelque chose sur le "code n'étant pas thread-safe" et j'ai donc fini par écrire un délégué (un exemple de MSDN a aidé) et l'appeler à la place.

Mais même ainsi, je ne comprenais pas tout à fait pourquoi tout le code supplémentaire était nécessaire.

Mise à jour: vais-je rencontrer des problèmes graves si je vérifie

Controls.CheckForIllegalCrossThread..blah =true

5
Typiquement, «thread safe» signifie tout ce que la personne qui utilise le terme pense que cela signifie, du moins pour cette personne. En tant que tel, ce n'est pas une construction de langage très utile - vous devez être beaucoup plus précis lorsque vous parlez du comportement du code threadé.


@dave Désolé, j'ai essayé de chercher, mais j'ai abandonné ... merci quand même ..
Vivek Bernard

1
un code qui ne se pose pasRace-Condition
Muhammad Babar

Réponses:


121

Eric Lippert a un joli billet de blog intitulé Quelle est cette chose que vous appelez "thread safe"? sur la définition de la sécurité des threads telle que trouvée sur Wikipedia

3 choses importantes extraites des liens:

"Un morceau de code est thread-safe s'il fonctionne correctement pendant l'exécution simultanée par plusieurs threads."

«En particulier, il doit satisfaire le besoin de plusieurs threads pour accéder aux mêmes données partagées,…»

"… Et la nécessité pour un élément de données partagé d'être accessible par un seul thread à un moment donné."

Vaut vraiment le détour!


25
Veuillez éviter les réponses aux liens uniquement, car elles pourraient devenir mauvaises à tout moment.
akhil_mittal


106

Dans le plus simple des termes, threadsafe signifie qu'il est sûr d'être accessible à partir de plusieurs threads. Lorsque vous utilisez plusieurs threads dans un programme et qu'ils essaient chacun d'accéder à une structure de données commune ou à un emplacement en mémoire, plusieurs problèmes peuvent survenir. Donc, vous ajoutez du code supplémentaire pour éviter ces mauvaises choses. Par exemple, si deux personnes écrivaient le même document en même temps, la deuxième personne à enregistrer écrasera le travail de la première personne. Pour sécuriser les threads, vous devez forcer la personne 2 à attendre que la personne 1 termine sa tâche avant de permettre à la personne 2 de modifier le document.


11
C'est ce qu'on appelle la synchronisation. Droite?
JavaTechnical

3
Oui. Forcer les différents threads à attendre l'accès à une ressource partagée peut être accompli avec la synchronisation.
Vincent Ramdhanie

D'après la réponse acceptée de Gregory, il dit: «Un morceau de code est thread-safe s'il fonctionne correctement pendant l'exécution simultanée par plusieurs threads.» pendant que vous dites "Pour rendre les threads sûrs alors, vous devez forcer la personne 1 à attendre"; ne dit-il pas que la simultanéité est acceptable alors que vous dites que ce n'est pas le cas? Pouvez-vous expliquer?
Chérie,

C'est la même chose. Je suggère juste un mécanisme simple comme exemple de ce qui rend le code threadsafe. quel que soit le mécanisme utilisé si plusieurs threads exécutant le même code ne doivent pas interférer entre eux.
Vincent Ramdhanie

Cela ne s'applique-t-il donc qu'au code utilisant des variables globales et statiques? En utilisant votre exemple de personnes éditant des documents, je suppose que cela n'a pas de sens d'empêcher la personne 2 d'exécuter le code d'écriture de document sur un autre document.
Aaron Franke

18

Wikipedia a un article sur la sécurité des threads.

Cette page de définitions (vous devez sauter une annonce - désolé) la définit ainsi:

Dans la programmation informatique, thread-safe décrit une partie de programme ou une routine qui peut être appelée à partir de plusieurs threads de programmation sans interaction indésirable entre les threads.

Un thread est un chemin d'exécution d'un programme. Un seul programme threadé n'aura qu'un seul thread et donc ce problème ne se pose pas. Pratiquement tous les programmes GUI ont plusieurs chemins d'exécution et donc des threads - il y en a au moins deux, un pour traiter l'affichage de l'interface graphique et gérer l'entrée de l'utilisateur, et au moins un autre pour effectuer réellement les opérations du programme.

Ceci est fait pour que l'interface utilisateur soit toujours réactive pendant que le programme fonctionne en déchargeant tout processus de longue durée sur tous les threads non-UI. Ces threads peuvent être créés une fois et exister pour la durée de vie du programme, ou simplement être créés lorsque cela est nécessaire et détruits lorsqu'ils sont terminés.

Comme ces threads devront souvent effectuer des actions communes - E / S de disque, sortie des résultats à l'écran, etc. - ces parties du code devront être écrites de manière à pouvoir gérer les appels à partir de plusieurs threads, souvent à le même temps. Cela impliquera des choses comme:

  • Travailler sur des copies de données
  • Ajout de verrous autour du code critique

8

Simplement, thread safe signifie qu'une méthode ou une instance de classe peut être utilisée par plusieurs threads en même temps sans aucun problème.

Considérez la méthode suivante:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

Maintenant, le thread A et le thread B aimeraient tous deux exécuter AddOne (). mais A commence en premier et lit la valeur de myInt (0) dans tmp. Maintenant, pour une raison quelconque, le planificateur décide d'arrêter le thread A et de reporter l'exécution au thread B. Le thread B lit maintenant aussi la valeur de myInt (toujours 0) dans sa propre variable tmp. Thread B termine la méthode entière, donc à la fin myInt = 1. Et 1 est retourné. C'est maintenant au tour de Thread A de nouveau. Le fil A continue. Et ajoute 1 à tmp (tmp était 0 pour le thread A). Et puis enregistre cette valeur dans myInt. myInt est à nouveau 1.

Donc, dans ce cas, la méthode AddOne a été appelée deux fois, mais parce que la méthode n'a pas été implémentée de manière thread-safe, la valeur de myInt n'est pas 2, comme prévu, mais 1 car le deuxième thread a lu la variable myInt avant la fin du premier thread le mettre à jour.

La création de méthodes thread-safe est très difficile dans les cas non triviaux. Et il y a pas mal de techniques. En Java, vous pouvez marquer une méthode comme synchronisée, cela signifie qu'un seul thread peut exécuter cette méthode à un moment donné. Les autres threads attendent en ligne. Cela rend une méthode sûre pour les threads, mais s'il y a beaucoup de travail à faire dans une méthode, cela gaspille beaucoup d'espace. Une autre technique consiste à `` marquer seulement une petite partie d'une méthode comme synchronisée ''en créant un verrou ou un sémaphore et en verrouillant cette petite partie (généralement appelée section critique). Certaines méthodes sont même implémentées en tant que thread safe sans verrou, ce qui signifie qu'elles sont construites de telle manière que plusieurs threads peuvent les parcourir en même temps sans jamais causer de problèmes, cela peut être le cas lorsqu'une méthode uniquement exécute un appel atomique. Les appels atomiques sont des appels qui ne peuvent pas être interrompus et qui ne peuvent être effectués que par un thread à la fois.


si la méthode AddOne a été appelée deux fois
Sujith PS

7

Dans le monde réel, l'exemple pour le profane est

Supposons que vous ayez un compte bancaire avec Internet et les services bancaires mobiles et que votre compte ne dispose que de 10 $. Vous avez effectué un transfert de solde vers un autre compte à l'aide de services bancaires mobiles, et entre-temps, vous avez effectué des achats en ligne en utilisant le même compte bancaire. Si ce compte bancaire n'est pas threadsafe, la banque vous permet d'effectuer deux transactions en même temps et la banque sera alors en faillite.

Threadsafe signifie que l'état d'un objet ne change pas si simultanément plusieurs threads tentent d'accéder à l'objet.


5

Vous pouvez obtenir plus d'explications dans le livre "Java Concurrency in Practice":

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 supplémentaire ou autre coordination de la part du code appelant.


4

Un module est thread-safe s'il garantit qu'il peut conserver ses invariants face à une utilisation multi-thread et concurrente.

Ici, un module peut être une structure de données, une classe, un objet, une méthode / procédure ou une fonction. Morceau de code essentiellement limité et données associées.

La garantie peut potentiellement être limitée à certains environnements tels qu'une architecture de processeur spécifique, mais doit être valable pour ces environnements. S'il n'y a pas de délimitation explicite des environnements, alors cela signifie généralement que pour tous les environnements, le code peut être compilé et exécuté.

Les modules thread-unsafe peuvent fonctionner correctement sous une utilisation multithread et simultanée, mais cela est souvent plus dû à la chance et au hasard qu'à une conception soignée. Même si certains modules ne se cassent pas pour vous, ils peuvent se briser lorsqu'ils sont déplacés vers d'autres environnements.

Les bogues multi-threads sont souvent difficiles à déboguer. Certains d'entre eux ne se produisent qu'occasionnellement, tandis que d'autres se manifestent de manière agressive - cela aussi peut être spécifique à l'environnement. Ils peuvent se manifester par des résultats subtilement faux ou des blocages. Ils peuvent perturber les structures de données de manière imprévisible et provoquer l'apparition d'autres bogues apparemment impossibles dans d'autres parties éloignées du code. Cela peut être très spécifique à une application, il est donc difficile de donner une description générale.


3

Sécurité des threads : un programme thread-safe protège ses données des erreurs de cohérence de la mémoire. Dans un programme hautement multithread, un programme thread-safe ne provoque aucun effet secondaire avec plusieurs opérations de lecture / écriture à partir de plusieurs threads sur les mêmes objets. Différents threads peuvent partager et modifier les données d'objet sans erreurs de cohérence.

Vous pouvez atteindre la sécurité des threads en utilisant l'API de concurrence avancée. Cette page de documentation fournit de bonnes constructions de programmation pour assurer la sécurité des threads.

Les objets de verrouillage prennent en charge les idiomes de verrouillage qui simplifient de nombreuses applications simultanées.

Les exécuteurs définissent une API de haut niveau pour le lancement et la gestion des threads. Les implémentations d'exécuteur fournies par java.util.concurrent fournissent une gestion de pool de threads adaptée aux applications à grande échelle.

Les collections simultanées facilitent la gestion de grandes collections de données et peuvent réduire considérablement le besoin de synchronisation.

Les variables atomiques ont des fonctionnalités qui minimisent la synchronisation et aident à éviter les erreurs de cohérence de la mémoire.

ThreadLocalRandom (dans JDK 7) fournit une génération efficace de nombres pseudo-aléatoires à partir de plusieurs threads.

Reportez-vous également aux packages java.util.concurrent et java.util.concurrent.atomic pour d'autres constructions de programmation.


1

Vous travaillez clairement dans un environnement WinForms. Les contrôles WinForms présentent une affinité de thread, ce qui signifie que le thread dans lequel ils sont créés est le seul thread qui peut être utilisé pour y accéder et les mettre à jour. C'est pourquoi vous trouverez des exemples sur MSDN et ailleurs montrant comment réorganiser l'appel sur le thread principal.

La pratique normale de WinForms consiste à avoir un seul thread dédié à tout votre travail d'interface utilisateur.


1

Je trouve le concept de http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 est ce que je considère généralement comme un threading dangereux, c'est-à-dire lorsqu'une méthode a et repose sur un effet secondaire tel qu'une variable globale.

Par exemple, j'ai vu du code qui formait des nombres à virgule flottante en chaîne, si deux d'entre eux sont exécutés dans des threads différents, la valeur globale de decimalSeparator peut être définitivement changée en '.'

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

-2

Pour comprendre la sécurité des threads, lisez les sections ci-dessous :

4.3.1. Exemple: Suivi de véhicule utilisant la délégation

Comme exemple plus substantiel de délégation, construisons une version du suiveur de véhicule qui délègue à une classe thread-safe. Nous stockons les emplacements sur une carte, donc nous commençons par un fil de sécurité mise en œuvre de la carte, ConcurrentHashMap. Nous stockons également l'emplacement en utilisant une classe Point immuable au lieu de MutablePoint, comme indiqué dans l'extrait 4.6.

Listing 4.6. Classe de point immuable utilisée par DelegatingVehicleTracker.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Pointest thread-safe car il est immuable. Les valeurs immuables peuvent être librement partagées et publiées, nous n'avons donc plus besoin de copier les emplacements lors de leur renvoi.

DelegatingVehicleTrackerdans le Listing 4.7 n'utilise aucune synchronisation explicite; tous les accès à l'état sont gérés par ConcurrentHashMap, et toutes les clés et valeurs de la carte sont immuables.

Listing 4.7. Délégation de la sécurité des threads à un ConcurrentHashMap.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

Si nous avions utilisé la MutablePointclasse d' origine au lieu de Point, nous casserions l'encapsulation en laissant getLocationspublier une référence à l'état mutable qui n'est pas thread-safe. Notez que nous avons légèrement modifié le comportement de la classe de suivi de véhicule; tandis que la version moniteur a renvoyé un instantané des emplacements, la version déléguée renvoie une vue non modifiable mais «en direct» des emplacements des véhicules. Cela signifie que si le thread A appelle getLocationset que le thread B modifie ultérieurement l'emplacement de certains des points, ces modifications sont reflétées dans la mappe renvoyée au thread A.

4.3.2. Variables d'état indépendant

Nous pouvons également déléguer la sécurité des threads à plus d'une variable d'état sous-jacente tant que ces variables d'état sous-jacentes sont indépendantes, ce qui signifie que la classe composite n'impose aucun invariant impliquant les multiples variables d'état.

VisualComponentdans le Listing 4.9 est un composant graphique qui permet aux clients d'enregistrer des écouteurs pour les événements de souris et de frappe. Il maintient une liste d'écouteurs enregistrés de chaque type, de sorte que lorsqu'un événement se produit, les écouteurs appropriés peuvent être appelés. Mais il n'y a pas de relation entre l'ensemble des écouteurs de souris et des écouteurs clés; les deux sont indépendants et VisualComponentpeuvent donc déléguer ses obligations de sécurité de thread à deux listes thread-safe sous-jacentes.

Liste 4.9. Délégation de la sécurité des threads à plusieurs variables d'état sous-jacentes.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponentutilise a CopyOnWriteArrayListpour stocker chaque liste d'écouteurs; il s'agit d'une implémentation de List sécurisée pour les threads particulièrement adaptée à la gestion des listes d'écouteurs (voir Section 5.2.3). Chaque liste est thread-safe, et comme il n'y a pas de contraintes couplant l'état de l'un à l'état de l'autre, VisualComponentpeut déléguer ses responsabilités de sécurité des threads aux objets mouseListenerset aux keyListenersobjets sous - jacents .

4.3.3. Lorsque la délégation échoue

La plupart des classes composites ne sont pas aussi simples que VisualComponent: elles ont des invariants qui relient leurs variables d'état de composant. NumberRangedans le Listing 4.10 utilise deux AtomicIntegerspour gérer son état, mais impose une contrainte supplémentaire - que le premier nombre soit inférieur ou égal au second.

Listing 4.10. Classe de plage de nombres qui ne protège pas suffisamment ses invariants. Ne fais pas ça.

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRangen'est pas thread-safe ; il ne préserve pas l'invariant qui contraint inférieur et supérieur. Les méthodes setLoweret setUppertentent de respecter cet invariant, mais le font mal. Les deux setLoweret setUppersont des séquences de contrôle puis d'action, mais ils n'utilisent pas un verrouillage suffisant pour les rendre atomiques. Si la plage de nombres tient (0, 10), et qu'un thread appelle setLower(5)tandis qu'un autre thread appelle setUpper(4), avec un certain timing malchanceux, les deux passeront les vérifications dans les setters et les deux modifications seront appliquées. Le résultat est que la plage contient maintenant (5, 4) - un état invalide . Ainsi, alors que les AtomicIntegers sous-jacents sont thread-safe, la classe composite ne l'est pas . Parce que les variables d'état sous-jacenteslower etupperne sont pas indépendants, NumberRangene peuvent pas simplement déléguer la sécurité des threads à ses variables d'état thread-safe.

NumberRangepourrait être rendu sans fil en utilisant le verrouillage pour maintenir ses invariants, tels que la protection inférieure et supérieure avec un verrou commun. Il doit également éviter de publier des valeurs inférieures et supérieures pour empêcher les clients de subvertir ses invariants.

Si une classe a des actions composées, comme le NumberRangefait, la délégation seule n'est pas encore une approche appropriée pour la sécurité des threads. Dans ces cas, la classe doit fournir son propre verrouillage pour garantir que les actions composées sont atomiques, sauf si l'action composée entière peut également être déléguée aux variables d'état sous-jacentes.

Si une classe est composée de plusieurs variables d'état indépendantes pour les threads et n'a pas d'opérations qui ont des transitions d'état non valides, alors elle peut déléguer la sécurité des threads aux variables d'état sous-jacentes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.