Un lecteur. Un écrivain. Quelques questions d’ordre général sur les mutex et les codages atomiques

J’ai un parent et un thread de travail qui partagent un drapeau bool et un std :: vector. Le parent ne lit que (c’est-à-dire qu’il lit le bool ou appelle my_vector.empty ()); le travailleur écrit seulement.

Mes questions:

  • Dois-je mutex protéger le drapeau booléen?

  • Puis-je dire que toutes les lectures / écritures bool sont des opérations insortingnsèquement atomiques? Si vous dites oui ou non, où avez-vous obtenu vos informations?

  • J’ai récemment entendu parler de GCC Atomic intégré . Puis-je les utiliser pour que mon drapeau lue / écrit atomique sans avoir à utiliser de mutex? Quelle est la différence? Je comprends que les commandes Atomic se résument au code machine, mais même les mutex se résument aux instructions de la barrière de mémoire du processeur, n’est-ce pas? Pourquoi les gens appellent-ils les mutex une construction “au niveau du système d’exploitation”?

  • Dois-je protéger par mutex mon vecteur std ::? Rappelez-vous que le thread de travail remplit ce vecteur, alors que le parent n’appelle que empty () dessus (c’est-à-dire qu’il ne le lit que)

  • Je ne crois pas que la protection mutex soit nécessaire pour le booléen ou le vecteur. Je rationalise comme suit: «Ok, si je lis la mémoire partagée juste avant sa mise à jour, c’est toujours correct, j’obtiendrai la valeur mise à jour la prochaine fois. Plus important encore, je ne vois pas pourquoi l’écrivain devrait être bloqué tant que la lecture est en train de lire, car après tout, le lecteur ne fait que lire! ”

Si quelqu’un peut me diriger dans la bonne direction, ce serait tout simplement génial. Je suis sur GCC 4.3 et Intel x86 32 bits. Merci beaucoup!

Dois-je mutex protéger le drapeau booléen?

Pas nécessairement, une instruction atomique ferait l’affaire. Par atomic instruction j’entends une fonction insortingnsèque du compilateur qui a) empêche le réordonnancement / l’optimisation du compilateur et b) entraîne une lecture / écriture atomique et c) émet une barrière de mémoire appropriée pour assurer la visibilité entre les processeurs (inutile pour les processeurs x86 actuels utilisant le cache MESI protocole de cohérence ). Similaire aux commandes intégrées atomiques de gcc .

Puis-je dire que toutes les lectures / écritures bool sont des opérations insortingnsèquement atomiques? Si vous dites oui ou non, où avez-vous obtenu vos informations?

Dépend de la CPU. Pour les processeurs Intel – oui. Consultez les manuels du développeur de logiciels pour architectures Intel® 64 et IA-32 .

J’ai récemment entendu parler de GCC Atomic intégré. Puis-je les utiliser pour que mon drapeau lue / écrit atomique sans avoir à utiliser de mutex? Quelle est la différence? Je comprends que les commandes Atomic se résument au code machine, mais même les mutex se résument aux instructions de la barrière de mémoire du processeur, n’est-ce pas? Pourquoi les gens appellent-ils les mutex une construction “au niveau du système d’exploitation”?

La différence entre atomics et mutex est que ce dernier peut mettre le thread en attente en veille jusqu’à ce que le mutex soit libéré. Avec atomics, vous ne pouvez que faire tourner votre ordinateur.

Dois-je protéger par mutex mon vecteur std ::? Rappelez-vous que le thread de travail remplit ce vecteur, alors que le parent n’appelle que empty () dessus (c’est-à-dire qu’il ne le lit que)

Tu fais.

Je ne crois pas que la protection mutex soit nécessaire pour le booléen ou le vecteur. Je rationalise comme suit: «Ok, si je lis la mémoire partagée juste avant sa mise à jour, c’est toujours correct, j’obtiendrai la valeur mise à jour la prochaine fois. Plus important encore, je ne vois pas pourquoi l’écrivain devrait être bloqué tant que la lecture est en train de lire, car après tout, le lecteur ne fait que lire! ”

Selon l’implémentation, vector.empty() peut impliquer la lecture et la soustraction ou la comparaison de deux pointeurs de début / fin de mémoire tampon. Il est donc possible que vous lisiez une nouvelle version d’un pointeur et une ancienne version d’un autre pointeur sans mutex. Un comportement surprenant peut en découler.

Du sharepoint vue des normes C ++ 11, vous devez protéger le booléen avec un mutex ou utiliser std::atomic . Même quand vous êtes sûr que votre nom booléen est lu et écrit atomiquement de toute façon, le compilateur a toujours la possibilité d’optimiser les access à distance car il ne connaît pas d’autres threads pouvant potentiellement y accéder.

Si, pour une raison quelconque, vous avez absolument besoin des dernières performances de votre plate-forme, lisez le “Manuel du développeur de logiciels pour architectures Intel 64 et IA-32”, qui vous expliquera comment les choses se passent sous votre capot. Mais bien sûr, cela rendra votre programme inportable.

Réponses:

  1. Vous devrez protéger la valeur booléenne (ou toute autre variable) pouvant être manipulée par deux threads ou plus en même temps. Vous pouvez le faire avec un mutex ou en opérant de manière atomique.
  2. Les lectures booléennes et les écritures bool peuvent être des opérations atomiques, mais deux opérations séquentielles ne le sont certainement pas (par exemple, une lecture puis une écriture). Plus sur cela plus tard.
  3. Les fonctions intégrées Atomic apportent une solution au problème ci-dessus: la possibilité de lire et d’écrire une variable dans une étape qui ne peut pas être interrompue par un autre thread. Cela rend l’opération atomique.
  4. Si vous utilisez l’indicateur bool comme votre “mutex” (c’est-à-dire, seul le fil qui définit l’indicateur bool sur true est autorisé à modifier le vecteur), vous êtes OK. L’exclusion mutuelle est gérée par le booléen, et tant que vous modifiez le booléen à l’aide d’opérations atomiques, vous devriez être prêt.
  5. Pour répondre à cela, laissez-moi utiliser un exemple:
 bool flag(false); std::vector my_vector; while (true) { if (flag == false) // check to see if the mutex is owned { flag = true; // obtain ownership of the flag (the mutex) // manipulate the vector flag = false; // release ownership of the flag } } 

Dans le code ci-dessus, dans un environnement multithread, il est possible de préempter le fil entre l’instruction if (la lecture) et l’affectation (l’écriture), ce qui signifie qu’il est possible pour deux (ou plus) threads avec ce type de code de les deux “possèdent” le mutex (et les droits sur le vecteur) en même temps. C’est pourquoi les opérations atomiques sont cruciales: elles garantissent que, dans le scénario ci-dessus, l’indicateur ne sera défini que par un thread à la fois, garantissant ainsi que le vecteur ne sera manipulé que par un thread à la fois.

Notez que la définition de l’indicateur sur false ne doit pas nécessairement être une opération atomique, car vous êtes la seule instance autorisée à le modifier.

Une solution approximative (lire: non testée) peut ressembler à:

 bool flag(false); std::vector my_vector; while (true) { // check to see if the mutex is owned and obtain ownership if possible if (__sync_bool_compare_and_swap(&flag, false, true)) { // manipulate the vector flag = false; // release ownership of the flag } } 

La documentation de la structure atomique se lit comme suit:

La version «bool» retourne true si la comparaison est réussie et si newval a été écrit.

Ce qui signifie que l’opération vérifiera si flag est false et si la valeur est définie sur true. Si la valeur était false, true est renvoyé, sinon false. Tout cela se passe dans une étape atomique, il est donc garanti de ne pas être préempté par un autre thread.

Je n’ai pas l’expertise nécessaire pour répondre à l’ensemble de votre question, mais votre dernière puce est incorrecte dans les cas où les lectures ne sont pas atomiques par défaut.

Un changement de contexte peut se produire n’importe où , le lecteur peut faire basculer le contexte en cours de lecture, l’écrivain peut être commuté et faire l’écriture complète, puis le lecteur termine sa lecture. Le lecteur ne verrait ni la première valeur, ni la deuxième valeur, mais potentiellement une valeur intermédiaire extrêmement imprécise.