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.