Question

Aujourd'hui, je suis tombé sur cette question:

vous avez un code

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}

Si worker serait appelé à partir de deux threads différents, quelle valeur sera counter ont après les deux sont terminées?

Je sais que cela pourrait être fait quoi que ce soit. Mais mes tripes internes me dit que counter++ sera probablement traduit en instruction assembleur unique, et si les deux fils sont exécutent sur le même noyau, counter sera 20.

Mais si ces fils sont exécutés sur des noyaux ou des processeurs différents, pourrait-il y avoir une condition de course dans leur microcode? une instruction assembleur est peut toujours être considérée comme opération atomique?

Était-ce utile?

La solution

Plus précisément pour x86, et en ce qui concerne votre exemple: counter++, il y a plusieurs façons il pourrait être compilé. L'exemple le plus trivial est:

inc counter

Cela se traduit par les micro-opérations suivantes:

  • charge counter à un registre caché sur la CPU
  • incrémenter le registre
  • stocker le registre mis à jour counter

Ceci est essentiellement le même que:

mov eax, counter
inc eax
mov counter, eax

Notez que si d'autres mises à jour de l'agent counter entre la charge et le magasin, il ne sera pas reflétée dans counter après le magasin. Cet agent pourrait être un autre fil dans le même noyau, un autre noyau de la même CPU, une autre CPU dans le même système, ou même un agent extérieur qui utilise DMA (Direct Memory Access).

Si vous voulez garantir que cette inc est atomique, utilisez le préfixe lock:

lock inc counter

lock garantit que personne ne peut mettre à jour counter entre la charge et le magasin.


En ce qui concerne les instructions plus compliquées, vous pouvez généralement pas supposer qu'ils vont exécuter atomiquement, à moins qu'ils ne prennent en charge le préfixe lock.

Autres conseils

La réponse est: cela dépend

Voici une certaine confusion, quelle instruction assembleur est. Normalement, une instruction assembleur est traduit en exactement une instruction de machine. Le excemption est lorsque vous utilisez des macros -. Mais vous devez être au courant de cette

Cela dit, la question se résume est une instruction machine atomique?

Dans le bon vieux temps, il était. Mais aujourd'hui, avec les CPU complexes, longues instructions de course, hyperthreading, ... ce n'est pas. Une garantie de processeurs que certains incrément / instructions de décrémentation sont atomiques. La raison en est qu'ils sont propres pour syncronizing très simple.

En outre, certaines commandes CPU ne sont pas si problématique. Lorsque vous avez une lecture simple (d'un morceau de données que le processeur peut chercher dans une seule pièce) - le chercher lui-même est bien sûr atomique, parce qu'il n'y a rien à partager du tout. Mais quand vous avez des données non alignés, il devient à nouveau complexe.

La réponse est: Cela dépend. Lisez attentivement le mode d'emploi de la machine du vendeur. Dans le doute, ce n'est pas!

Edit: Oh, je l'ai vu maintenant, vous demandez aussi ++ contre. La déclaration « plus susceptible d'être traduit » ne peut pas faire confiance du tout. Cela dépend aussi en grande partie sur le compilateur bien sûr! Il devient plus difficile lorsque le compilateur fait différentes optimisations.

Pas toujours -. Sur certaines architectures une instruction d'assemblage est traduit en une instruction de code machine, tandis que sur d'autres, il ne

De plus - vous pouvez jamais supposons que la langue du programme que vous utilisez est la compilation d'une ligne en apparence simple de code dans une instruction de montage. De plus, sur certaines architectures, vous ne pouvez pas supposer un code machine exécutera atomiquement.

Utiliser des techniques de synchronisation appropriés à la place, en fonction de la langue que vous codez dans.

  1. incrément / décrément sur les opérations de 32 bits ou moins variables entières sur un seul processeur 32 bits sans la technologie Hyper-threading sont atomiques.
  2. Sur un processeur avec technologie Hyper-Threading ou sur un système multi-processeur, l'incrément / décrément opérations ne sont pas garanties à exécuter atomicaly.

invalidés par le commentaire de Nathan: Si je me souviens de mon assembleur Intel x86 correctement, l'instruction INC ne fonctionne que pour les registres et ne travaille pas directement pour les emplacements de mémoire.

Alors un compteur ++ ne serait pas une seule instruction en assembleur (juste en ignorant la partie post-incrémentation). Il serait au moins trois instructions: charge variable compteur pour enregistrer, registre d'incrément, registre de charge retour au comptoir. Et qui est juste pour l'architecture x86.

En bref, ne comptez pas sur qu'il soit atomique à moins qu'il soit spécifié par la spécification du langage et que le compilateur que vous utilisez prend en charge les spécifications.

Non, vous ne pouvez pas assumer cela. À moins clairement indiqué dans la spécification du compilateur. Et d'ailleurs personne ne peut garantir qu'une instruction assembleur simple effet atomique. Dans la pratique, chaque instruction assembleur est traduit en nombre d'opérations de microcode - UOP.
également la question de la condition de course est étroitement couplé avec le modèle de mémoire (cohérence, séquentielle, la cohérence et la libération, etc.), pour chacun d'eux la réponse et le résultat pourrait être différent.

Une autre question est que si vous ne déclarez pas la variable comme volatile, le code généré réviserait probablement pas la mémoire à chaque itération de la boucle, seulement à la fin de la boucle la mémoire serait mis à jour.

Peut-être pas une réponse réelle à votre question, mais (en supposant que cela est C #, ou une autre langue .NET) si vous voulez counter++ vous pourriez vraiment être atomique multi-thread, utilisez System.Threading.Interlocked.Increment(counter).

Voir d'autres réponses pour l'information sur les réelles de différentes façons pourquoi / comment counter++ ne pouvait pas être atomique. ; -)

Dans la plupart des cas, pas . En fait, sur x86, vous pouvez effectuer l'instruction

push [address]

qui, en C, serait quelque chose comme:

*stack-- = *address;

effectue deux transferts de mémoire dans une instruction .

C'est pratiquement impossible de le faire en 1 cycle d'horloge, pas moins parce que un transfert de mémoire est également impossible dans un cycle!

Sur de nombreux autres processeurs, le système de séparation entre la mémoire et le processeur est plus grand. (Souvent ce processeur peut être peu ou big-endian selon le système de mémoire, comme ARM et PowerPC), cela a aussi des conséquences sur le comportement atomique si le système de mémoire peut réordonner lit et écrit.

A cet effet, il existe des barrières de mémoire ( http://en.wikipedia.org/wiki/ Memory_barrier )

Donc en bref, tandis que les instructions atomiques sont assez sur intel (avec les préfixes de verrouillage pertinents), plus doit être fait sur la non-intel, puisque la mémoire E / S peut ne pas être dans le même ordre.

Ceci est un problème connu lors du portage « lock sans » solutions d'Intel à d'autres architectures.

(Notez que multiprocesseur (non multicore) systèmes sur x86 semblent également avoir besoin de barrières de mémoire, au moins en mode 64 bits.

Je pense que vous aurez une condition de course sur l'accès.

Si vous vouliez assurer une opération atomique à incrémenter contre vous auriez besoin d'utiliser ++ compteur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top