2010-09-09 16 views
4

Supposons que j'ai deux threads A et B qui incrémentent tous les deux un ~ count ~ global ~ variable. Chaque thread exécute une boucle comme celui-ci:pthreads: Si j'incrémente un global à partir de deux threads différents, peut-il y avoir des problèmes de synchronisation?

for(int i=0; i<1000; i++) 
    count++; //alternatively, count = count + 1; 

dire chaque incrément de fil comptent 1000 fois, et disons que le nombre commence à 0. Il peut y avoir des problèmes de synchronisation dans ce cas? Ou comptera correctement égal à 2000 lorsque l'exécution est terminée? Je suppose que l'instruction "count = count + 1" peut se décomposer en DEUX instructions d'assemblage, il est possible que l'autre thread soit permuté entre ces deux instructions? Pas certain. Qu'est-ce que tu penses?

Répondre

3

Le compteur doit clairement être protégé par un mutex ou un autre mécanisme de synchronisation.

À un niveau fondamental, le nombre ++ statment se décompose en:

load count into register 
increment register 
store count from register 

Un changement de contexte pourrait se produire avant/après l'une de ces étapes, ce qui conduit à des situations telles que:

Thread 1: load count into register A (value = 0) 
Thread 2: load count into register B (value = 0) 
Thread 1: increment register A (value = 1) 
Thread 1: store count from register A (value = 1) 
Thread 2: increment register B (value = 1) 
Thread 2: store count from register B (value = 1) 

Comme vous peut voir, les deux threads ont complété une itération de la boucle, mais le résultat net est que le compte n'a été incrémenté qu'une seule fois.

Vous voudrez probablement également faire en sorte que les charges & soient stockées en mémoire, car un bon optimiseur devrait probablement être compté dans un registre sauf indication contraire.

Aussi, je suggère que si c'est tout le travail qui va être fait dans vos threads, les performances baissent considérablement de tout le verrouillage/déverrouillage mutex nécessaire pour le garder cohérent. Les threads doivent avoir des unités de travail beaucoup plus grandes à effectuer.

+0

Volatile ne force pas les charges et les magasins à aller à la mémoire, pas de la manière implicite ici. Volatile permet au compilateur de savoir que pour la variable en question, sa valeur peut changer sans la connaissance du compilateur, par ex. dites une horloge en temps réel. Cela signifie que le compilateur ne comptera pas sur les copies de registre de cette valeur. Cela n'empêche cependant pas le CPU de différer, par exemple, l'écriture de cette ligne de cache dans la mémoire aussi longtemps que possible (ce qui est le cas, car la mémoire écrit détruit les performances). –

11

Oui, il peut y avoir des problèmes de synchronisation dans ce cas. Vous devez soit protéger la variable count avec un mutex, soit utiliser une opération atomique (généralement spécifique à la plate-forme).

Exemple d'utilisation mutex pthread

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

for(int i=0; i<1000; i++) { 
    pthread_mutex_lock(&mutex); 
    count++; 
    pthread_mutex_unlock(&mutex); 
} 

utilisant ops atomiques

Il y a une discussion préalable ops atomiques spécifiques de la plate-forme ici: UNIX Portable Atomic Operations

Si vous avez seulement besoin de soutenir GCC, cette approche est simple. Si vous soutenez d'autres compilateurs, vous devrez probablement prendre des décisions par plateforme.

+0

+1 pour la suggestion d'opérations atomiques. –

3

Oui, il peut y avoir.

Il n'y a aucune garantie qu'un incrément est lui-même une opération atomique.

Par exemple, si un thread lit la valeur pour incrément obtient alors troqué, l'autre thread pourrait venir et le changer, le premier thread écrire de nouveau la valeur incorrecte:

+-----+ 
| 0 | Value stored in memory (0). 
+-----+ 
| 0 | Thread 1 reads value into register (r1 = 0). 
+-----+ 
| 0 | Thread 2 reads value into register (r2 = 0). 
+-----+ 
| 1 | Thread 2 increments r2 and writes back. 
+-----+ 
| 1 | Thread 1 increments r1 and writes back. 
+-----+ 

Vous peut voir que, même si les deux threads ont essayé d'incrémenter la valeur, il est seulement augmenté d'un. C'est juste l'un des problèmes possibles. Il se peut même que l'écriture elle-même ne soit pas atomique et qu'un thread puisse mettre à jour seulement une partie de la valeur avant d'être échangé.

Utiliser des mutex. C'est ce qu'ils sont pour.

+0

Le problème que vous avez illustré n'a guère de sens sur les processeurs modernes. Généralement, le problème n'est pas la mise à jour atomique de plusieurs octets car les machines mettent généralement à jour un mot à la fois de manière atomique. En outre, tout ce dont vous avez besoin pour être en sécurité à partir de votre exemple est un mot clé volatile. Le problème que vous voyez habituellement en pratique est beaucoup plus simple: deux threads lisent la même valeur, chacun l'incrémente indépendamment dans un registre, puis réécrit la même valeur. – blucz

+1

@blucz, j'ai fourni une situation plus simple (en cours pendant que vous étiez en train de taper votre commentaire par son apparence) mais vous avez tout à fait tort. Les threads POSIX ne font pas de suppositions sur le matériel sur lequel ils s'exécutent et si leurs écritures sont atomiques ou non. Si vous allez suivre une norme, vous devriez le suivre. Si vous voulez introduire d'autres hypothèses, c'est bon, soyez conscient des ramifications et ne prétendez pas suivre la norme :-) – paxdiablo

+0

Je pense que vous avez mal compris mon commentaire. Je n'ai rien dit à propos de la norme pthread. Je viens de suggérer que dans la pratique, l'exemple que vous donniez était très improbable, ce qui était le cas, pour la raison que j'ai donnée. Est-il possible d'avoir cette erreur? Bien sûr ... sur un système que 99,999% d'entre nous ne verra jamais. – blucz