9
votes

Un CPU attribue-t-il une valeur atomiquement à la mémoire?

Une question rapide que je m'interrogee depuis un certain temps; Est-ce que la CPU assigne des valeurs atomiquement ou, est-ce un peu budgétaire (disons par exemple un entier 32 bits).
Si c'est un peu budgété, un autre fil peut accéder à cet emplacement exact obtiendra une "pièce" de la valeur attribuée à être attribué?

pense à ceci:
J'ai deux threads et une variable partagée «non signée INT» (appelez-le «g_uival»).
Les deux fils boucles.
Sur est l'impression "g_uival" avec printf ("% u \ n", g_uival).
La seconde augmente ce nombre.
Le fil d'impression imprimera-t-il quelque chose qui n'est totalement pas ou partie de la valeur de "g_uival"?

dans le code: xxx


0 commentaires

7 Réponses :


3
votes

Je crois que la seule réponse correcte est "Cela dépend". Sur ce que vous pouvez demander?

Bien pour les démarreurs que la CPU. Mais aussi, certains processeurs sont atomiques pour écrire des valeurs de largeur de mot, mais uniquement lorsqu'ils sont alignés. Ce n'est vraiment pas quelque chose que vous pouvez garantir à un niveau de langage C.

De nombreux compilateurs offrent des "intrinsics" pour émettre des opérations atomiques correctes. Ce sont des extensions qui agissent comme des fonctions, mais émettent le code correct pour votre architecture cible pour obtenir les opérations atomiques nécessaires. Par exemple: http://gcc.gnu.org/onlineDocs/gcc/atomic -Builtins.html


0 commentaires

5
votes

dépend des largeurs de bus de la CPU et de la mémoire. Dans un contexte PC, avec quelque chose d'autre qu'un CPU vraiment ancien, les accès allant jusqu'à 32 bits sont atomiques; Les accès 64 bits peuvent être ou non. Dans l'espace intégré, de nombreux processeurs (la plupart?) Sont de 32 bits de large et il n'y a aucune disposition pour rien plus large, votre intz_t est garanti d'être non atomique.


4 commentaires

Est-il possible d'accéder à des valeurs 32 bits qui croient des lignes de cache ces jours-ci?


@Lasse: De nombreux processeurs de bureau modernes permettent des lectures et écrites non alignées, mais à une pénalité de performance significative. Les processeurs plus anciens ou plus petits (pour par exemple les dispositifs intégrés) ont tendance à ne pas. Depuis quelque temps, cela a été divisé entre les processeurs CISC (avaient tendance à soutenir des lectures et des écrit non alignés) et des processeurs de RISC (non), mais les distinctions sont floues ici.


Les microcontrôleurs 8, 16 et 32 ​​bits sont tous courants. Sur une AVR (avec des charges et des magasins 8 bits), il est possible d'avoir une autre une autre solution ISR (interruption de la routine d'interruption) ou un autre thread (si vous exécutez un système d'exploitation multitâche préemptable) écrivant une partie de la variable (ou simplement lire une partie de la changer que le fil précédent fait).


La plupart des processeurs intégrés sont 8 ou 16 bits et de nombreux processeurs 4 bits sont toujours utilisés. Avec ceux-ci, une opération 32 bits n'est pas atomique. D'autre part, il y a aussi des processeurs de 64 bits. Je travaille actuellement avec TI 64x Series DSP, qui est considérée comme un processeur 32 bits, mais elle peut accéder à la mémoire de données interne via un bus de données de 64 bits (en fait 2 x 64 bits bus) et 64 bits (et peut-être même 128 Bit) Les opérations sont atomiques.



0
votes

ajouter à ce qui a été dit jusqu'à présent - une autre préoccupation potentielle est la mise en cache. Les processeurs ont tendance à travailler avec le cache de mémoire local (sur matrice) qui peut être immédiatement revint à la mémoire principale. Si la boîte comporte plus d'une CPU, il est possible qu'un autre processeur ne verra pas les modifications pendant un certain temps après la modification de la CPU, à moins que certaines commandes de synchronisation n'indiquent à tous les CPP de synchroniser leurs caches sur la matrice. Comme vous pouvez l'imaginer, cette synchronisation peut considérablement ralentir le traitement.


2 commentaires

Mais dans ce cas, comme il n'y a pas de synchronisation entre le consommateur et le producteur, cela ne change pas vraiment le comportement. Bien sûr, le consommateur pourrait lire une vieille valeur, mais il ne serait pas possible de dire s'il était dû à des caches sur matrices non synchronisées ou à la planification seulement. Ce que je reçois, c'est que le consommateur ne lirait jamais une valeur partiellement écrite en raison de caches non synchronisées


Tout dépend de ce qui est attendu. Si l'intention est de produire des valeurs uniques - bien, ce problème peut introduire des doublons



0
votes

N'oubliez pas que le compilateur assume un seul thread lors de l'optimisation, et tout cela pourrait disparaître.


11 commentaires

Même si c'est une variable globale qui est clairement utilisée dans d'autres fonctions? Je doute que tout compilateur serait que impoli :)


@Isak Savo: Variable globale non volatile statique (non-externe)? bien sûr, pourquoi pas? Marquez toutes les variables utilisées pour le contrôle de la concurrence comme volatiles, cela empêche les optimisations du compilateur liées à ces variables.


@Liori: Mais même dans une seule application filetée, c'est un code parfaitement valide. Mauvaise architecture de côté, il n'ya rien de mal à avoir une fonction modifier les variables globales sans utiliser le résultat eux-mêmes.


@Isak Savo: Je ne dis pas que ceci est un code invalide. Ce que je dis seulement, c'est que tant que le code se comporte de la même manière que ceux spécifiés, le compilateur peut faire n'importe quoi ... comme en supprimant des variables globales inutiles.


@Liori: Entièrement d'accord. Mais dans ce cas, la variable est clairement utilisée. Il est envoyé sous forme d'argument à imprimerf et pour tout le compilateur sait que Printf () pourrait empêcher la fin du monde.


@Isak Savo: le compilateur sait que le fil dans lequel la fonction thread_reader () est appelée ne modifie pas cette variable et la variable n'est pas volatile. Par conséquent, il peut supposer que sa valeur ne changera pas dans la boucle et de charger la valeur de la variable au registre de la CPU une fois avant la boucle. Afaik GCC le fera avec -O3.


(Je ne peux pas reproduire que maintenant ... mais je suis sûr que GCC faisait cela)


@LIORI: Ce code sera donc rompu par GCC? int myvar = 0; vide f1 () {myvar = 3; } void f2 () {printf ("% d", myvar); } int main () {F1 (); F2 (); retour 0;} . Je m'attendrais à ce que le code imprime 3 à l'écran.


@Isak Savo: Tant que tout est dans un fil, cela fonctionnera comme vous vous attendez.


ISAK, si vous ne l'appelez jamais dans la période intermédiaire, le compilateur peut simplement appuyer sur la valeur sur la pile et la laisser là-bas. Pendant votre boucle, l'autre fonction n'est jamais appelée et donc le compilateur est parfaitement autorisé à l'optimiser. Il ne peut pas connaître votre modèle de filetage. Tant que votre code ne casse pas dans un seul fil, le compilateur est autorisé à le faire. Ce n'est pas le travail du compilateur de laisser travailler multi-thread.


@Deadmg: Ah, je vois maintenant ce que vous et Liori signifie. J'étais coincé dans une affaire plus générale dans ma tête, mais je regarde à nouveau le code de l'OP, je comprends comment le compilateur peut l'optimiser puisqu'il ne changera pas pendant la boucle (aussi loin que le compilateur se soucie de toute façon). Merci d'avoir expliqué



1
votes

Vous avez dit « bit par bit » dans votre question. Je ne pense pas que l'architecture fait un peu des opérations à la fois, sauf avec certains bus de protocole série spécialisés. lecture de la mémoire standard / écriture sont effectuées avec 8, 16, 32 ou 64 bits de granularité. Il est donc possible de l'opération dans votre exemple est atomique.

Cependant, la réponse est fortement dépendante de la plate-forme.

  • Cela dépend des capacités du CPU. le matériel peut faire une 32 bits atomique opération? Voici un indice: Si le variable que vous travaillez est plus grande à la taille du registre d'origine (par exemple, 64 bits int sur un système 32 bits), il est certainement pas atomique.
  • Cela dépend de la façon dont le compilateur génère le code machine. Ça pourrait ont transformé votre variable 32 bits l'accès en mémoire 4x 8 bits lit.
  • Il devient délicat si l'adresse de ce vous accédez n'est pas aligné à travers mot naturel d'une machine frontière. Vous pouvez frapper un cache faute ou erreur de page.

    Il est très possible que vous verriez une valeur corrompue ou inattendue en utilisant l'exemple de code que vous avez publié.

    Votre plate-forme fournit probablement une méthode de faire des opérations atomiques. Dans le cas d'une plate-forme Windows, il est par Interlocked fonctions . Dans le cas de Linux / Unix, regardez le type atomic_t .


0 commentaires

0
votes

POSIX définit le type spécial SIG_ATOMIC_T Quelles garanties qui l'écrit sont atomiques par rapport aux signaux, ce qui le rendra aussi atomique du point de vue des autres threads comme vous le souhaitez. Ils ne définissent pas spécifiquement un type de filetage frontal atomique comme celui-ci, car la communication de fil devrait être médiée par des mutiles ou d'autres primitives de sychronisation.


0 commentaires

0
votes

Considérant les microprocesseurs modernes (et ignorer les microcontrôleurs), l'affectation 32 bits est atomique, non bit-bit.

Cependant, maintenant complètement éteint du sujet de votre question ... Le thread d'impression pourrait toujours imprimer quelque chose qui n'est pas attendu en raison du manque de synchronisation dans cet exemple, bien sûr, en raison de la réorganisation des instructions et de multiples noyaux chacun avec leur propre Copie de g_uival dans leurs caches.


0 commentaires