Le C ++ - faq est généralement administré par la communauté C ++, et vous pouvez venir nous demander votre avis dans notre chat.
Puppy
@DeadMG: J'ignorais le C ++ - faq et son étiquette, cela a été suggéré dans un commentaire.
K-ballo
2
Où avez-vous entendu dire que const signifie thread-safe?
Mark B
2
@Mark B: Herb Sutter et Bjarne Stroustrup le disaient à la Standard C ++ Foundation , voir le lien au bas de la réponse.
K-ballo
NOTE À CEUX VENANT ICI: la vraie question n'est PAS de savoir si cela constsignifie thread-safe. Ce serait absurde, car sinon, cela signifierait que vous devriez être en mesure de marquer chaque méthode thread-safe comme const. Au contraire, la question que nous posons vraiment est constIMPLIES thread-safe, et c'est le sujet de cette discussion.
user541686
Réponses:
132
J'entends que cela constsignifie thread-safe en C ++ 11 . Est-ce vrai?
C'est un peu vrai ...
Voici ce que dit le langage standard sur la sécurité des threads:
[1.10 / 4]
Deux évaluations d'expression sont en conflit si l'une d'elles modifie un emplacement mémoire (1.7) et l'autre accède ou modifie le même emplacement mémoire.
[1.10 / 21]
L'exécution d'un programme contient une course aux données si elle 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 indéfini.
ce qui n'est rien d'autre que la condition suffisante pour qu'une course aux données se produise:
Il y a deux ou plusieurs actions exécutées en même temps sur une chose donnée; et
Au moins l'un d'entre eux est une écriture.
La bibliothèque standard s'appuie sur cela, en allant un peu plus loin:
[17.6.5.9/1]
Cette section spécifie les exigences que les implémentations doivent satisfaire pour empêcher les courses de données (1.10). Chaque fonction de bibliothèque standard doit satisfaire à chaque exigence sauf indication contraire. Les implémentations peuvent empêcher les courses de données dans des cas autres que ceux spécifiés ci-dessous.
[17.6.5.9/3]
Une fonction de bibliothèque standard C ++ ne doit pas modifier directement ou indirectement les objets (1.10) accessibles par des threads autres que le thread courant à moins que les objets ne soient accédés directement ou indirectement via lesargumentsnon const de la fonction, y compristhis.
qui en termes simples dit qu'il s'attend à ce que les opérations sur les constobjets soient thread-safe . Cela signifie que la bibliothèque standard n'introduira pas de course aux données tant que les opérations sur les constobjets de vos propres types non plus
Se compose entièrement de lectures - c'est-à-dire qu'il n'y a pas d'écritures -; ou
Synchronise les écritures en interne.
Si cette attente ne tient pas pour l'un de vos types, son utilisation directe ou indirecte avec n'importe quel composant de la bibliothèque standard peut entraîner une course aux données . En conclusion, constcela signifie thread-safe du point de vue de la bibliothèque standard . Il est important de noter qu'il ne s'agit que d'un contrat et qu'il ne sera pas appliqué par le compilateur, si vous le rompez, vous obtenez un comportement indéfini et vous êtes seul. Que ce constsoit présent ou non n'affectera pas la génération de code - du moins pas en ce qui concerne les courses de données -.
Est -ce que cela veut dire constest maintenant l'équivalent de Java desynchronized ?
Non . Pas du tout...
Considérez la classe trop simplifiée suivante représentant un rectangle:
La fonction membrearea est thread-safe ; non pas parce constque c'est, mais parce qu'il consiste entièrement en opérations de lecture. Aucune écriture n'est impliquée et au moins une écriture impliquée est nécessaire pour qu'une course aux données se produise. Cela signifie que vous pouvez appeler à areapartir d'autant de threads que vous le souhaitez et que vous obtiendrez des résultats corrects à tout moment.
Notez que cela ne signifie pas que rectc'est thread-safe . En fait, il est facile de voir comment si un appel à areadevait se produire en même temps qu'un appel à set_sizesur une donnée rect, alors areapourrait finir par calculer son résultat basé sur une ancienne largeur et une nouvelle hauteur (ou même sur des valeurs brouillées) .
Mais c'est bien, rectn'est-ce pas constsi ce n'est même pas censé être thread-safe après tout. Un objet déclaré const rect, en revanche, serait thread-safe car aucune écriture n'est possible (et si vous envisagez const_cast-ing quelque chose initialement déclaré, constvous obtenez un comportement indéfini et c'est tout).
Alors qu'est-ce que cela signifie alors?
Supposons - pour des raisons d'argumentation - que les opérations de multiplication sont extrêmement coûteuses et il vaut mieux les éviter lorsque cela est possible. Nous pourrions calculer la zone uniquement si elle est demandée, puis la mettre en cache au cas où elle serait à nouveau demandée à l'avenir:
[Si cet exemple semble trop artificiel, vous pouvez le remplacer mentalement intpar un très grand entier alloué dynamiquement qui est par nature non thread-safe et pour lequel les multiplications sont extrêmement coûteuses.]
La fonction membrearea n'est plus thread-safe , elle effectue des écritures maintenant et n'est pas synchronisée en interne. C'est un problème? L'appel à areapeut se produire dans le cadre d'un constructeur de copie d'un autre objet, un tel constructeur aurait pu être appelé par une opération sur un conteneur standard , et à ce stade, la bibliothèque standard s'attend à ce que cette opération se comporte comme une lecture en ce qui concerne les courses de données . Mais nous faisons des écritures!
Dès que nous mettons un rectdans un conteneur standard - directement ou indirectement - nous concluons un contrat avec la bibliothèque standard . Pour continuer à faire des écritures dans une constfonction tout en respectant ce contrat, nous devons synchroniser ces écritures en interne:
Notez que nous avons rendu la areafonction thread-safe , mais que ce rectn'est toujours pas thread-safe . Un appel à se areaproduire en même temps qu'un appel à set_sizepeut encore finir par calculer la mauvaise valeur, car les affectations à widthet heightne sont pas protégées par le mutex.
Si nous voulions vraiment un thread-saferect , nous utiliserions une primitive de synchronisation pour protéger le non-thread-saferect .
Sont-ils à court de mots clés ?
Oui, ils sont. Ils sont à court de mots clés depuis le premier jour.
@Ben Voigt: Je crois comprendre que la spécification C ++ 11 pour std::stringest formulée d'une manière qui interdit déjà COW . Je ne me souviens pas des détails, cependant ...
K-ballo
3
@BenVoigt: Non. Cela empêcherait simplement de telles choses d'être désynchronisées, c'est-à-dire qu'elles ne sont pas sûres pour les threads. C ++ 11 interdit déjà explicitement COW - ce passage particulier n'a rien à voir avec cela, cependant, et n'interdirait pas COW.
Puppy
2
Il me semble qu'il y a une lacune logique. [17.6.5.9/3] interdit "trop" en disant "il ne doit pas modifier directement ou indirectement"; il devrait dire "ne doit pas introduire directement ou indirectement une course aux données", à moins qu'une écriture atomique ne soit quelque part définie comme n'étant pas une "modification". Mais je ne trouve ça nulle part.
Andy Prowl
1
J'ai probablement clarifié mon argument un peu plus ici: isocpp.org/blog/2012/12/ ... Merci d'avoir essayé de m'aider quand même.
Andy Prowl
1
parfois je me demande qui était celui (ou ceux directement impliqués) réellement responsable de la rédaction de certains paragraphes standards comme ceux-ci.
const
signifie thread-safe. Ce serait absurde, car sinon, cela signifierait que vous devriez être en mesure de marquer chaque méthode thread-safe commeconst
. Au contraire, la question que nous posons vraiment estconst
IMPLIES thread-safe, et c'est le sujet de cette discussion.Réponses:
C'est un peu vrai ...
Voici ce que dit le langage standard sur la sécurité des threads:
ce qui n'est rien d'autre que la condition suffisante pour qu'une course aux données se produise:
La bibliothèque standard s'appuie sur cela, en allant un peu plus loin:
qui en termes simples dit qu'il s'attend à ce que les opérations sur les
const
objets soient thread-safe . Cela signifie que la bibliothèque standard n'introduira pas de course aux données tant que les opérations sur lesconst
objets de vos propres types non plusSi cette attente ne tient pas pour l'un de vos types, son utilisation directe ou indirecte avec n'importe quel composant de la bibliothèque standard peut entraîner une course aux données . En conclusion,
const
cela signifie thread-safe du point de vue de la bibliothèque standard . Il est important de noter qu'il ne s'agit que d'un contrat et qu'il ne sera pas appliqué par le compilateur, si vous le rompez, vous obtenez un comportement indéfini et vous êtes seul. Que ceconst
soit présent ou non n'affectera pas la génération de code - du moins pas en ce qui concerne les courses de données -.Non . Pas du tout...
Considérez la classe trop simplifiée suivante représentant un rectangle:
La fonction membre
area
est thread-safe ; non pas parceconst
que c'est, mais parce qu'il consiste entièrement en opérations de lecture. Aucune écriture n'est impliquée et au moins une écriture impliquée est nécessaire pour qu'une course aux données se produise. Cela signifie que vous pouvez appeler àarea
partir d'autant de threads que vous le souhaitez et que vous obtiendrez des résultats corrects à tout moment.Notez que cela ne signifie pas que
rect
c'est thread-safe . En fait, il est facile de voir comment si un appel àarea
devait se produire en même temps qu'un appel àset_size
sur une donnéerect
, alorsarea
pourrait finir par calculer son résultat basé sur une ancienne largeur et une nouvelle hauteur (ou même sur des valeurs brouillées) .Mais c'est bien,
rect
n'est-ce pasconst
si ce n'est même pas censé être thread-safe après tout. Un objet déclaréconst rect
, en revanche, serait thread-safe car aucune écriture n'est possible (et si vous envisagezconst_cast
-ing quelque chose initialement déclaré,const
vous obtenez un comportement indéfini et c'est tout).Supposons - pour des raisons d'argumentation - que les opérations de multiplication sont extrêmement coûteuses et il vaut mieux les éviter lorsque cela est possible. Nous pourrions calculer la zone uniquement si elle est demandée, puis la mettre en cache au cas où elle serait à nouveau demandée à l'avenir:
[Si cet exemple semble trop artificiel, vous pouvez le remplacer mentalement
int
par un très grand entier alloué dynamiquement qui est par nature non thread-safe et pour lequel les multiplications sont extrêmement coûteuses.]La fonction membre
area
n'est plus thread-safe , elle effectue des écritures maintenant et n'est pas synchronisée en interne. C'est un problème? L'appel àarea
peut se produire dans le cadre d'un constructeur de copie d'un autre objet, un tel constructeur aurait pu être appelé par une opération sur un conteneur standard , et à ce stade, la bibliothèque standard s'attend à ce que cette opération se comporte comme une lecture en ce qui concerne les courses de données . Mais nous faisons des écritures!Dès que nous mettons un
rect
dans un conteneur standard - directement ou indirectement - nous concluons un contrat avec la bibliothèque standard . Pour continuer à faire des écritures dans uneconst
fonction tout en respectant ce contrat, nous devons synchroniser ces écritures en interne:Notez que nous avons rendu la
area
fonction thread-safe , mais que cerect
n'est toujours pas thread-safe . Un appel à searea
produire en même temps qu'un appel àset_size
peut encore finir par calculer la mauvaise valeur, car les affectations àwidth
etheight
ne sont pas protégées par le mutex.Si nous voulions vraiment un thread-safe
rect
, nous utiliserions une primitive de synchronisation pour protéger le non-thread-saferect
.Oui, ils sont. Ils sont à court de mots clés depuis le premier jour.
Source : Vous ne savez pas
const
etmutable
- Herb Sutterla source
std::string
est formulée d'une manière qui interdit déjà COW . Je ne me souviens pas des détails, cependant ...