61 votes

Les types fondamentaux C/C++ sont-ils atomiques ?

Les types fondamentaux C/C++, comme int , double etc., atomique, par exemple threadsafe ?

Sont-ils exempts de courses de données, c'est-à-dire que si un thread écrit dans un objet de ce type et qu'un autre thread le lit, le comportement est-il bien défini ?

Si non, cela dépend-il du compilateur ou d'autre chose ?

70voto

James Adkison Points 6334

Non, les types de données fondamentales (par ex, int , double ) ne sont pas atomiques, voir std::atomic .

Au lieu de cela, vous pouvez utiliser std::atomic<int> o std::atomic<double> .

Nota: std::atomic a été introduit avec le C++11 et je crois savoir qu'avant le C++11, la norme C++ ne reconnaissait pas du tout l'existence du multithreading.


Comme l'a souligné @Josh, std::atomic_flag est un type booléen atomique. Il est garantie sans serrure contrairement à la std::atomic les spécialisations.


La documentation citée provient de : http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf . Je suis presque sûr que la norme n'est pas libre et que ce n'est donc pas la version finale/officielle.

1.10 Exécutions multithreads et courses de données

  1. Deux évaluations d'expressions entrent en conflit si l'une d'elles modifie un emplacement de mémoire (1.7) et que l'autre lit ou modifie le même emplacement de mémoire.

  2. La bibliothèque définit un certain nombre d'opérations atomiques (Clause 29) et d'opérations sur les mutex (Clause 30) qui sont spécialement identifiées comme des opérations de synchronisation. Ces opérations jouent un rôle particulier en rendant les affectations d'un thread visibles pour un autre. Une opération de synchronisation sur un ou plusieurs emplacements de mémoire est soit une opération de consommation, soit une opération d'acquisition, soit une opération de libération, soit à la fois une opération d'acquisition et de libération. Une opération de synchronisation sans emplacement de mémoire associé est une clôture et peut être soit une clôture d'acquisition, soit une clôture de libération, soit à la fois une clôture d'acquisition et de libération. En outre, il existe des opérations atomiques relaxées, qui ne sont pas des opérations de synchronisation, et des opérations atomiques de lecture-modification-écriture, qui présentent des caractéristiques particulières.

  3. Deux actions sont potentiellement concurrentes si
    (23.1) - ils sont exécutés par des fils différents, ou
    (23.2) - ils ne sont pas séquencés, et au moins un est exécuté par un gestionnaire de signaux.
    L'exécution d'un programme contient une course aux données si elle contient deux actions conflictuelles potentiellement concurrentes, dont l'une au moins n'est pas atomique, et qu'aucune ne se produit avant l'autre, sauf dans le cas particulier des gestionnaires de signaux décrit ci-dessous. Une telle course aux données entraîne un comportement non défini.

29.5 Types atomiques

  1. Il existe des spécialisations explicites du modèle atomique pour les types intégraux "char, signed char , unsigned char , short , unsigned short , int , unsigned int , long , unsigned long , long long , unsigned long long , char16_ t, char32_t , wchar_t et tout autre type requis par les typedefs de l'en-tête. <cstdint> . Pour chaque intégrale de type intégral, la spécialisation atomic<integral> fournit des opérations atomiques supplémentaires appropriées aux types intégraux. Il existe une spécialisation atomic<bool> qui fournit les opérations atomiques générales comme spécifié en 29.6.1

  2. Il doit y avoir des spécialisations partielles de pointeur du modèle de classe atomique. Ces spécialisations doivent avoir une présentation standard, des constructeurs par défaut triviaux et des destructeurs triviaux. Elles doivent toutes supporter la syntaxe d'initialisation agrégée.

29.7 Type de drapeau et opérations

  1. Les opérations sur un objet de type atomic_flag doivent être sans verrou. [Note : Les opérations doivent donc également être sans adresse. Aucun autre type n'exige d'opérations sans verrou, de sorte que le type atomic_flag est le type minimum implémenté au niveau matériel nécessaire pour se conformer à cette norme internationale. Les autres types peuvent être émulés avec atomic_flag, mais avec des propriétés moins qu'idéales. - note de fin ]

17voto

Andrew Henle Points 5156

Puisque C est également (actuellement) mentionné dans la question bien qu'il ne soit pas dans les balises, le C Standard estados:

5.1.2.3 Exécution du programme

...

Lorsque le traitement de la machine abstraite est interrompu par la réception d'un signal, les valeurs des objets qui ne sont ni des objets atomiques libres de sans verrouillage ni de type volatile sig_atomic_t ne sont pas spécifiés, tout comme l'est l'état de l'environnement à virgule flottante. La valeur de tout objet modifié par le gestionnaire qui n'est ni un objet atomique sans verrou ni un objet de type volatile sig_atomic_t devient indéterminé lorsque le gestionnaire gestionnaire, tout comme l'état de l'environnement en virgule flottante s'il est modifié modifié par le gestionnaire et n'est pas restauré à son état d'origine.

y

5.1.2.4 Exécutions multithreads et courses de données

...

Deux évaluations de l'expression conflit si l'un d'eux modifie un emplacement de mémoire et que l'autre lit ou modifie le même emplacement de mémoire.

[plusieurs pages de normes - certains paragraphes traitent explicitement des types atomiques].

L'exécution d'un programme contient un course aux données s'il contient deux actions conflictuelles dans des threads différents, dont au moins une n'est pas atomique, et aucune ne se produit avant l'autre. Une telle course aux données entraîne un comportement non défini.

Notez que les valeurs sont "indéterminées" si un signal interrompt le traitement, et l'accès simultané à des types qui ne sont pas explicitement atomiques est un comportement non défini.

12voto

Emily L. Points 696

Qu'est-ce qu'un atome ?

Atomique, comme décrivant quelque chose ayant la propriété d'un atome. Le mot atome vient du latin atomus signifiant "indivis".

En général, je considère qu'une opération atomique (quel que soit le langage) possède deux qualités :

Une opération atomique est toujours indivise.

C'est-à-dire qu'elle est effectuée de manière indivisible, je crois que c'est ce que le PO appelle "threadsafe". En un sens, l'opération se produit instantanément lorsqu'elle est vue par un autre thread.

Par exemple, l'opération suivante est probablement divisée (selon le compilateur et le matériel) :

i += 1;

parce qu'il peut être observé par un autre fil (sur un matériel et un compilateur hypothétiques) comme :

load r1, i;
addi r1, #1;
store i, r1;

Deux fils effectuant l'opération ci-dessus i += 1 sans une synchronisation appropriée peut produire un mauvais résultat. Dites i=0 initialement, le fil T1 charges T1.r1 = 0 et le fil T2 charges t2.r1 = 0 . Les deux fils incrémentent leurs r1 par 1, puis enregistre le résultat dans i . Bien que deux incréments aient été effectués, la valeur de i n'est toujours que de 1 car l'opération d'incrémentation était divisible. Notez que s'il y avait eu une synchronisation avant et après i+=1 l'autre thread aurait attendu que l'opération soit terminée et aurait donc observé une opération non divisée.

Notez que même une écriture simple peut ou non être indivise :

i = 3;

store i, #3;

en fonction du compilateur et du matériel. Par exemple, si l'adresse de i n'est pas aligné correctement, il faut alors utiliser un chargement/stockage non aligné qui est exécuté par le CPU sous la forme de plusieurs chargements/stockages plus petits.

Une opération atomique a une sémantique d'ordonnancement de la mémoire garantie.

Les opérations non atomiques peuvent être réordonnées et ne se produisent pas nécessairement dans l'ordre écrit dans le code source du programme.

Par exemple, dans le cadre du Règle du "comme si". le compilateur est autorisé à réorganiser les stockages et les chargements comme il l'entend, à condition que tous les accès à la mémoire volatile se fassent dans l'ordre spécifié par le programme, "comme si" le programme était évalué selon les termes de la norme. Ainsi, les opérations non atomiques peuvent être réorganisées sans tenir compte de l'ordre d'exécution dans un programme multithread. C'est pourquoi l'utilisation apparemment innocente d'une fonction raw int comme variable de signalisation dans la programmation multithread est cassé, même si les écritures et les lectures peuvent être indivisibles, l'ordonnancement peut casser le programme selon le compilateur. Une opération atomique impose l'ordre des opérations qui l'entourent en fonction de la sémantique de la mémoire spécifiée. Voir std::memory_order .

Le processeur peut également réorganiser vos accès à la mémoire en fonction des contraintes d'ordonnancement de la mémoire de ce processeur. Vous pouvez trouver les contraintes d'ordonnancement de la mémoire pour l'architecture x86 dans le fichier Manuel du développeur de logiciels pour les architectures Intel 64 et IA32 section 8.2 à partir de la page 2212.

Les types primitifs ( int , char etc.) ne sont pas atomiques

Parce que même si, dans certaines conditions, ils peuvent avoir des instructions de stockage et de chargement indivisibles, voire même certaines instructions arithmétiques, ils ne garantissent pas l'ordre des stockages et des chargements. En tant que tels, ils ne sont pas sûrs à utiliser dans des contextes multithreads sans une synchronisation appropriée pour garantir que l'état de la mémoire observé par les autres threads est ce que vous pensez qu'il est à ce moment-là.

J'espère que cela explique pourquoi Les types primitifs ne sont pas atomiques.

6voto

Matt McNabb Points 14273

Une information supplémentaire que je n'ai pas vue mentionnée dans les autres réponses jusqu'à présent :

Si vous utilisez std::atomic<bool> par exemple, et bool est réellement atomique sur l'architecture cible, alors le compilateur ne générera pas de clôtures ou de verrous redondants. Le même code sera généré comme pour une simple commande bool .

En d'autres termes, en utilisant std::atomic ne rend le code moins efficace que si cela est réellement nécessaire pour la correction de la plate-forme. Il n'y a donc aucune raison de l'éviter.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X