Supposons que nous ayons le bit de code suivant:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> void guarantee(bool cond, const char *msg) { if (!cond) { fprintf(stderr, "%s", msg); exit(1); } } bool do_shutdown = false; // Not volatile! pthread_cond_t shutdown_cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t shutdown_cond_mutex = PTHREAD_MUTEX_INITIALIZER; /* Called in Thread 1. Intended behavior is to block until trigger_shutdown() is called. */ void wait_for_shutdown_signal() { int res; res = pthread_mutex_lock(&shutdown_cond_mutex); guarantee(res == 0, "Could not lock shutdown cond mutex"); while (!do_shutdown) { // while loop guards against spurious wakeups res = pthread_cond_wait(&shutdown_cond, &shutdown_cond_mutex); guarantee(res == 0, "Could not wait for shutdown cond"); } res = pthread_mutex_unlock(&shutdown_cond_mutex); guarantee(res == 0, "Could not unlock shutdown cond mutex"); } /* Called in Thread 2. */ void trigger_shutdown() { int res; res = pthread_mutex_lock(&shutdown_cond_mutex); guarantee(res == 0, "Could not lock shutdown cond mutex"); do_shutdown = true; res = pthread_cond_signal(&shutdown_cond); guarantee(res == 0, "Could not signal shutdown cond"); res = pthread_mutex_unlock(&shutdown_cond_mutex); guarantee(res == 0, "Could not unlock shutdown cond mutex"); }
4 Réponses :
Bien sûr, les normes C et C ++ actuelles ne disent rien sur le sujet. P>
Autant que je sache, POSIX évite toujours de définir officiellement un modèle de concurrence (je peux être obsolète, cependant, auquel cas appliquez ma réponse uniquement aux versions POSIX précédentes). Par conséquent, ce que cela dit doit être lu avec un peu de sympathie - il ne pose pas précisément les exigences dans ce domaine, mais les entreprises devraient "savoir ce que cela signifie" et faire quelque chose qui fait des discussions utilisables. P>
Lorsque la norme indique que les mutexes "synchronisent l'accès à la mémoire", les implémentations doivent supposer que cela signifie que les modifications apportées sous la serrure dans un thread seront visibles sous la serrure d'autres threads. En d'autres termes, il est nécessaire (bien que pas suffisant) que les opérations de synchronisation incluent des barrières de mémoire d'un type ou d'un autre, et le comportement nécessaire d'une barrière de mémoire est qu'il doit assumer que les globaux peuvent changer. P>
Je suis sûr de 99% que quelque part Posix définit le verrouillage et le déverrouillage de Mutex en tant que barrières de mémoire complètes, mais je ne peux pas le trouver maintenant.
Pthreads applique une barrière mémoire sur pthread_cond_wait () code>: Stackoverflow.com/questions/3208060/...
@Michael, @R ..: droite, il définit les fonctions "synchroniser la mémoire", mais cela ne définit pas ce que signifie réellement la "mémoire de synchroniser". Il y en a plus sur cela dans le papier de Boehm, tout ce que je dis, sera fondamentalement ma lecture (probablement imparfaite) de ses recherches. Il n'y a pas beaucoup de controverse ce que cela signifie, cela signifie quelles «barrières de mémoire» réellement sur le matériel connu, ainsi qu'un comportement complexe du compilateur pour garantir que les barrières ne sont pas subverties par certains types de réchérisation. Ce n'est tout simplement pas Posix qui dit cela, c'est le désir compréhensible des écrivains compilateurs de fournir des outils utiles.
Donc, le «si la norme n'interdit pas cela, il est autorisé« L'approche de la question gagne essentiellement, si un implauteur choisit de prendre cette ligne. Vous pouvez produire un tas de déchets, avec une synchronisation de données parfois non viable et affirmer plausiblement qu'elle est conforme à la lettre de la norme. Selon Boehm.
Je vais avoir une lecture étroite de ce papier. Au premier coup d'œil, on dirait que cela dit que Pthreads ne peut garantir que la sécurité du fil avec la coopération du compilateur.
@Michael: C'est certainement vrai et dit en outre que la spécification Pthreads ne définit pas de manière adéquate la coopération nécessaire au compilateur.
Juste pour être clair, l'OP demande ... cache une variable dans un registre dans un appel de bibliothèque Pthread? Code>. Dans ce cas particulier, la qualification code> code> dans la question permet d'utiliser des barrières de mémoire animées. Aucune barrière de mémoire ne changera le contenu d'un registre.
@JOHNE: Je pense que le questionneur signifie: "Utilisez un registre pour mettre en cache une variable", pas "Cache une variable qui est déjà dans un registre". Je comprends que la question de savoir si le compilateur doit recharger à partir de la mémoire après l'appel, ou s'il peut continuer à utiliser la valeur qu'il a lu à partir de la mémoire plus tôt. Si la variable elle-même était marquée enregistrer code> qui changerait effectivement au jeu, car son adresse ne peut être prise, ce qui signifie qu'il ne peut pas être aliasé, ne peut pas être modifié par un autre code et Tout le problème disparaît. Mais dans la question
do_shutdown code> n'est pas marqué
enregistrer code>, c'est un global.
@Stevejessop " Si la variable elle-même était marquée enregistrement qui changerait effectivement le jeu, puisque son adresse ne peut être prise i>" Cela pourrait être pris en C ++. Quoi qu'il en soit, enregistrer code> change strictement rien, car le compilateur sait très bien si l'adresse d'une variable est prise ou non. Même si c'est global. En fait, son adresse n'est pas prise. En fait, c'est tout à fait sans importance.
@R " J'ai 59% sûr que quelque part Posix définit le verrouillage de mutex et le déverrouillage comme des barrières de mémoire complète i>" POSIX ne définit pas pthread _ code> fonctions en tant que barrières de mémoire CPU. POSIX définit l'effet en termes de lecture et d'écrit du programme, pas en trimestre de la CPU. Cette sous-analyse relie les niveaux.
@Stevejessop " Il dit que la spécification Pthreads ne définit pas de manière adéquate la coopération nécessaire du compilateur. I>" que de dire quelque chose de trivial: le thread de POSIX est une API standard pour les programmes, pas un ABI pour les compilateurs. . Il n'est pas prévu d'expliquer comment une mise en œuvre (compilateur + POSIX) devrait coopérer.
@Stevejessop " Vous pouvez produire une pile de déchets, avec une synchronisation de données parfois non viable et affirmer plausiblement qu'elle est conforme à la lettre de la norme. I>" Il est complètement évident qu'un compilateur qui utilise Une adresse mémoire fixe dans la convention d'appel de la fonction serait conforme et ne permettrait pas beaucoup de multi-threading.
" Si l'objet est non volatile et que le compilateur peut prouver que ce fil ne le modifie pas i>" Le compilateur ne peut évidemment pas prouver que cela.
@CuciousGuy: Je pense que vous devriez relire la partie de la question que je répondais ici. Cette partie de la question est: "Si le compilateur peut appeler un appel de fonction, et le prouver qu'il n'accède pas do_shutdown code> peut alors
do_shutdown code> être mis en cache. Qu'en est-il d'une inlication fonction dans le même tu "? J'ai répondu "oui aux deux". Vous avez (peut-être accidentellement) affirmé qu'un compilateur ne peut jamais prouver si une fonction particulière dans le même TU modifie une mondiale particulière. C'est faux. Pour certaines fonctions, cela peut prouver exactement que, pour l'exemple le plus évident supposé que la fonction soit vide.
Je prétend que le compilateur ne peut éventuellement prouver que pour une fonction Pthread, ni pour une fonction de SYSCALL, ou appelle ou contient ASM. Sauf si vous mettez l'annotation pour permettre au compilateur de faire cela (voir Syntaxe ASM GCC).
@CuriousGuy: OK, mais ce n'est pas ce dont je parlais dans la partie de ma réponse que vous avez citée. Il y a beaucoup de fonctions pour lesquelles le compilateur peut le prouver, ce sont ceux que l'intervenant est intéressé. Il existe également de nombreuses fonctions pour lesquelles le compilateur ne peut pas le prouver - le plus notamment pour lequel ce n'est pas vrai, et La fonction fait i> modifie la variable. Mais aussi comme vous le dites, diverses conditions peuvent empêcher le compilateur de savoir quelle fonction une fonction.
La question initiale est " le compilateur pourrait savoir hypothétiquement que pthread_cond_wait () code> ne modifie pas do_shutdown. I>" donc j'ai compris que vous avez référé à des fonctions appelant directement ou indirectement
pthread_cond_wait code>.
Etant donné que Autant que je sache, rien ne dit rien directement à ce sujet dans la norme, sauf que la machine abstraite (à une seule filetée) La norme utilise pour définir le comportement des expressions indique que la variable doit être lue lorsqu'il est accessible dans une expression. La standard autorise la lecture de la variable à optimiser uniquement si le comportement peut être prouvé "comme si" était rechargé. Et cela ne peut arriver que si le compilateur peut savoir que la valeur n'a pas été modifiée par l'appel de la fonction. P>
Aussi pas que la bibliothèque Pthread ne garantit pas certaines garanties sur les barrières de mémoire pour diverses fonctions, y compris maintenant, si Donc, dans ce cas, vous devriez utiliser des mécanismes pour vous assurer que la valeur a été rechargée à travers l'appel. Notez que, en raison des entraçabilité matérielles, le mot clé code> volatile code> peut ne pas être suffisant pour assurer une commande d'accès à la mémoire correcte - vous devriez compter sur des API fournies par Pthreads ou le système d'exploitation pour vous assurer que. (En tant que note latérale, les versions récentes des compilateurs de Microsoft do document que do_shutdown code> a une liaison externe, le compilateur peut savoir ce qui se passe à travers l'appel (à moins que cela n'ait une visibilité complète des fonctions appelées). Il faudrait donc recharger la valeur (volatil ou non-filetage n'a aucune incidence sur cela) après l'appel. P>
pthread_cond_wait () code>: Gardant une variable avec une garantie de Mutex Pthread, il n'est pas non plus caché? p>
do_shutdown code> était statique (pas de liaison externe) et que plusieurs threads utilisaient cette variable statique définie dans le même module (c'est-à-dire que l'adresse de la variable statique n'a jamais été prise à être transmis à un autre module), cela pourrait être une histoire différente. Par exemple, disons que vous avez une seule fonction qui utilise une telle variable et a commencé plusieurs instances de thread en cours d'exécution pour cette fonction. Dans ce cas, une implémentation de compilateur conformément aux normes pourrait mettre en cache la valeur entre les appels de fonction, car il pourrait supposer que rien d'autre ne pourrait modifier la valeur (le modèle de machine abstrait de la norme n'inclut pas le filetage). P>
volatile code> appliquent des barrières de mémoire complètes, mais j'ai lu des opinions indiquant que cela n'est pas requis par la norme). P>
Étant donné que la question est "tout compilateur conforme aux normes", cela inclut peut-être celui de l'ensemble de la bibliothèque standard, et toutes les dépendances, sont statiquement liés, ainsi que des optimisations de liaison / programme entiers, l'appel est entièrement visible? Peut-être pas un scénario réaliste.
C'est ce que dit le bon sens, malheureusement, il est difficile de savoir si elle est en quelque sorte appliquée / garantie par les normes.
Si le compilateur a des connaissances de programme entières, cela pourrait mettre en cache la valeur dans l'appel de la fonction s'il est déterminé que rien ne pourrait modifier la valeur. Mais la bibliothèque Pthreads est conçue pour empêcher la mise en cache sur un appel qui pourrait modifier une telle valeur (la bibliothèque peut avoir besoin d'effectuer quelque chose de non-standard / de mise en œuvre spécifique pour le faire, mais c'est le problème de la bibliothèque à résoudre).
" qui pourrait être une histoire différente. I>" S'il vous plaît montrer le code hypothétique pour cette histoire différente. " Une implémentation de compilateur conforme aux normes peut mettre en cache la valeur de la fonction d'appel i>".
" Volatile Appliquez des obstacles à la mémoire complète, mais j'ai lu des opinions qui indiquent que cela n'est pas requis par la norme i>" Ce n'est pas simplement une "opinion". En général volatile code> n'a rien à voir avec MT. Pthreads Ne dis rien sur
volatile code> b>.
De mon propre travail, je peux dire que oui, le compilateur peut cacher des valeurs sur pthread_mutex_lock / pthread_mutex_unlock. J'ai passé la majeure partie du week-end à traquer un bug dans un peu de code causé par un ensemble de missions de pointeurs en cache et non disponibles aux threads qui les avaient besoin. En test rapide, j'ai enveloppé les affectations dans un verrouillage / déverrouillage mutex et les threads n'ont toujours pas accès aux valeurs de pointeur appropriées. Déplacer les assignations de pointeur et le verrouillage mutex associé à une fonction distincte résolvent le problème. P>
Il n'y a aucun moyen p>
Le compilateur pourrait savoir hypothétiquement que pthread_cond_wait () ne modifie pas do_shutdown. p>
blockQuote>
Si vous croyez différemment, veuillez afficher une preuve: un programme complet C ++ tel qu'un compilateur non conçu pour MT pourrait déduire que C'est absurde, un compilateur ne peut pas comprendre ce que font les fonctions
pthread_cond_wait code> ne modifie pas
do_shutdown p >
pthread _ code>, sauf si elle a Connaissance intégrée forte> des threads POSIX. P>
Oui, mais cela peut le faire si et seulement s'il n'y a pratiquement aucune manière légitime, la fonction de la bibliothèque pourrait modifier la valeur de la variable (par exemple s'il s'agit d'une variable automatique et son adresse n'est jamais prise).
@R: Correct ... et dans ce cas, il est en fait sûr de le faire, car aucun autre thread ne pouvait utiliser cette variable non plus.