J'ai une liste dont je veux que différents threads récupèrent des éléments. Afin d'éviter de verrouiller le mutex gardant la liste lorsqu'elle est vide, je vérifie empty()
avant de verrouiller.
Ce n'est pas grave si l'appel list::empty()
n'est pas correct dans 100% des cas. Je veux seulement éviter de planter ou de perturber les appels list::push()
et les list::pop()
appels simultanés .
Suis-je sûr de supposer que VC ++ et Gnu GCC ne se tromperont que parfois empty()
et rien de pire?
if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
mutex.lock();
if(list.empty() == false){ // check again while locked to be certain
element = list.back();
list.pop_back();
}
mutex.unlock();
}
c++
multithreading
optimization
race-condition
stdlist
Anne Quinn
la source
la source
std::list::size
a garanti une complexité temporelle constante, ce qui signifie essentiellement que la taille (nombre de nœuds) doit être stockée dans une variable distincte; appelons celasize_
.std::list::empty
puis renvoie probablement quelque chose commesize_ == 0
, et la lecture et l'écriture simultanéessize_
provoqueraient la course aux données, donc UB.Réponses:
Non, ça ne va pas. Si vous vérifiez si la liste est vide en dehors d'un mécanisme de synchronisation (verrouillage du mutex), vous avez une course aux données. Avoir une course aux données signifie que vous avez un comportement indéfini. Avoir un comportement indéfini signifie que nous ne pouvons plus raisonner sur le programme et toute sortie que vous obtenez est "correcte".
Si vous appréciez votre santé mentale, vous prendrez le coup de la performance et verrouillerez le mutex avant de vérifier. Cela dit, la liste n'est peut-être même pas le bon conteneur pour vous. Si vous pouvez nous dire exactement ce que vous en faites, nous pourrons peut-être vous proposer un meilleur conteneur.
la source
list::empty()
est une action de lecture qui n'a rien à voir avecrace-condition
read-write
ouwrite-write
. Si vous ne me croyez pas, lisez la section standard sur les courses de donnéesIl y a une lecture et une écriture (très probablement pour le
size
membre destd::list
, si nous supposons qu'il est nommé ainsi) qui ne sont pas synchronisées entre elles . Imaginez qu'un fil appelleempty()
(dans votre extérieurif()
) tandis que l'autre fil entre dans l'intérieurif()
et s'exécutepop_back()
. Vous lisez alors une variable en cours de modification. Il s'agit d'un comportement indéfini.la source
Comme exemple de la façon dont les choses pourraient mal tourner:
Un compilateur suffisamment intelligent pourrait voir que
mutex.lock()
cela ne peut pas changer lalist.empty()
valeur de retour et ainsi ignorerif
complètement la vérification interne , ce qui conduirait finalement à unpop_back
sur une liste dont le dernier élément aurait été supprimé après le premierif
.Pourquoi peut-il faire ça? Il n'y a pas de synchronisation
list.empty()
, donc si elle était modifiée simultanément, cela constituerait une course aux données. La norme dit que les programmes ne doivent pas avoir de races de données, donc le compilateur prendra cela pour acquis (sinon il ne pourrait effectuer quasiment aucune optimisation). Par conséquent, il peut supposer une perspective monothread sur le non synchronisélist.empty()
et conclure qu'il doit rester constant.Il ne s'agit que d'une parmi plusieurs optimisations (ou comportements matériels) susceptibles de casser votre code.
la source
a.load()+a.load()
...a.load()*2
est évidente? Même pasa.load(rel)+b.load(rel)-a.load(rel)
optimisé de toute façon. Rien n'est. Pourquoi vous attendez-vous à ce que les verrous (qui, par essence, ont principalement une cohérence seq) soient plus optimisés?a.load() + a.load()
à2 * a.load()
. N'hésitez pas à poser une question à ce sujet si vous souhaitez en savoir plus.