C ++ 14 semble avoir omis un mécanisme pour vérifier si an std::mutex
est verrouillé ou non. Voir cette question SO:
/programming/21892934/how-to-assert-if-a-stdmutex-is-locked
Il existe plusieurs façons de contourner cela, par exemple en utilisant;
std::mutex::try_lock()
std::unique_lock::owns_lock()
Mais aucune de ces solutions n'est particulièrement satisfaisante.
try_lock()
est autorisé à retourner un faux négatif et a un comportement indéfini si le thread actuel a verrouillé le mutex. Il a également des effets secondaires. owns_lock()
nécessite la construction d'un unique_lock
sur le dessus de l'original std::mutex
.
Évidemment, je pourrais rouler le mien, mais je préfère comprendre les motivations de l'interface actuelle.
La capacité de vérifier le statut d'un mutex (par exemple std::mutex::is_locked()
) ne me semble pas être une demande ésotérique, donc je soupçonne que le Comité de normalisation a délibérément omis cette caractéristique plutôt que d'être une erreur.
Pourquoi?
Edit: Ok donc peut-être que ce cas d'utilisation n'est pas aussi courant que je m'y attendais, donc je vais illustrer mon scénario particulier. J'ai un algorithme d'apprentissage automatique qui est distribué sur plusieurs threads. Chaque thread fonctionne de manière asynchrone et retourne dans un pool maître une fois qu'il a terminé un problème d'optimisation.
Il verrouille ensuite un mutex maître. Le thread doit ensuite choisir un nouveau parent à partir duquel muter une progéniture, mais ne peut choisir que des parents qui n'ont actuellement pas de progéniture qui sont optimisés par d'autres threads. J'ai donc besoin d'effectuer une recherche pour trouver des parents qui ne sont pas actuellement verrouillés par un autre thread. Il n'y a aucun risque que l'état du mutex change pendant la recherche, car le mutex du thread maître est verrouillé. Évidemment, il existe d'autres solutions (j'utilise actuellement un indicateur booléen), mais je pensais que le mutex offre une solution logique à ce problème, car il existe à des fins de synchronisation inter-thread.
is_locked
?Réponses:
Je peux voir au moins deux problèmes graves avec l'opération suggérée.
Le premier a déjà été mentionné dans un commentaire de @ gnasher729 :
La seule façon de s'assurer que la propriété «est actuellement verrouillé» d'un mutex ne change pas est de bien le verrouiller vous-même.
Le deuxième problème que je vois est que, sauf si vous verrouillez un mutex, votre thread ne se synchronise pas avec le thread qui avait précédemment verrouillé le mutex. Par conséquent, il n'est même pas bien défini de parler «avant» et «après» et si le mutex est verrouillé ou non revient à se demander si le chat de Schrödiger est actuellement en vie sans tenter d'ouvrir la boîte.
Si je comprends bien, les deux problèmes seraient sans objet dans votre cas particulier grâce au verrouillage du mutex maître. Mais cela ne me semble pas être un cas particulièrement courant, je pense donc que le comité a fait ce qu'il fallait en n'ajoutant pas de fonction qui pourrait être quelque peu utile dans des scénarios très spéciaux et causer des dommages dans tous les autres. (Dans l'esprit de: "Rendre les interfaces faciles à utiliser correctement et difficiles à utiliser incorrectement.")
Et si je peux dire, je pense que la configuration que vous avez actuellement n'est pas la plus élégante et pourrait être refactorisée pour éviter tout problème. Par exemple, au lieu que le thread principal vérifie tous les parents potentiels pour celui qui n'est pas actuellement verrouillé, pourquoi ne pas conserver une file d'attente de parents prêts? Si un thread veut en optimiser un autre, il saute le suivant de la file d'attente et dès qu'il a de nouveaux parents, il les ajoute à la file d'attente. De cette façon, vous n'avez même pas besoin du thread principal en tant que coordinateur.
la source
Il semble que vous utilisiez les mutex secondaires non pas pour verrouiller l'accès à un problème d'optimisation, mais pour déterminer si un problème d'optimisation est optimisé en ce moment ou non.
C'est totalement inutile. J'aurais une liste de problèmes qui doivent être optimisés, une liste de problèmes en cours d'optimisation en ce moment et une liste de problèmes qui ont été optimisés. (Ne prenez pas littéralement «liste», pensez à «toute structure de données appropriée).
Les opérations d'ajout d'un nouveau problème à la liste des problèmes non optimisés, ou de déplacement d'un problème d'une liste à l'autre, seraient effectuées sous la protection du mutex "maître" unique.
la source
std::mutex
est approprié pour une telle structure de données?std::mutex
repose sur une implémentation de mutex définie par le système d'exploitation qui peut très bien prendre des ressources (par exemple des poignées) qui sont limitées et lentes à allouer et / ou à exploiter. L'utilisation d'un seul mutex pour verrouiller l'accès à une structure de données interne est susceptible d'être beaucoup plus efficace et peut-être aussi plus évolutive.Comme d'autres l'ont dit, il n'y a pas de cas d'utilisation où
is_locked
sur un mutex est d'un quelconque avantage, c'est pourquoi la fonction n'existe pas.Le cas avec lequel vous rencontrez un problème est incroyablement courant, c'est essentiellement ce que font les threads de travail, qui sont l'une, sinon la mise en œuvre la plus courante des threads.
Vous avez une étagère avec 10 boîtes dessus. Vous avez 4 travailleurs travaillant avec ces boîtes. Comment vous assurez-vous que les 4 travailleurs travaillent sur des boîtes différentes? Le premier travailleur prend une boîte sur l'étagère avant de commencer à y travailler. Le deuxième travailleur voit 9 boîtes sur l'étagère.
Il n'y a pas de mutex pour verrouiller les boîtes, donc voir l'état du mutex imaginaire sur la boîte n'est pas nécessaire, et abuser d'un mutex en tant que booléen est tout simplement faux. Le mutex verrouille l'étagère.
la source
En plus des deux raisons données dans la réponse de 5gon12eder ci-dessus, je voudrais ajouter que ce n'est ni nécessaire ni souhaitable.
Si vous détenez déjà un mutex, vous feriez mieux de savoir que vous le tenez! Vous n'avez pas besoin de demander. Tout comme avec un bloc de mémoire ou toute autre ressource, vous devez savoir exactement si vous le possédez ou non, et quand il convient de libérer / supprimer la ressource.
Si ce n'est pas le cas, votre programme est mal conçu et vous vous dirigez vers des ennuis.
Si vous devez accéder à la ressource partagée protégée par le mutex et que vous ne possédez pas déjà le mutex, vous devez acquérir le mutex. Il n'y a pas d'autre option, sinon la logique de votre programme n'est pas correcte.
Vous pouvez trouver le blocage acceptable ou inacceptable, dans les deux cas
lock()
outry_lock()
donner le comportement que vous souhaitez. Tout ce que vous devez savoir, positivement et sans aucun doute, c'est si vous avez réussi à acquérir le mutex (la valeur de retour detry_lock
vous l'indique). Peu importe que quelqu'un d'autre le détienne ou que vous ayez un faux échec.Dans tous les autres cas, carrément, cela ne vous regarde pas. Vous n'avez pas besoin de savoir, et vous ne devriez pas savoir, ni faire d'hypothèses (pour les problèmes d'actualité et de synchronisation mentionnés dans l'autre question).
la source
try_lock
la première ressource, et si celle-ci échoue, essayez la seconde. Exemple: Trois connexions persistantes et regroupées au serveur de base de données, et vous devez en utiliser une pour envoyer une commande.is_locked()
cela pourrait faciliter un tel comportement.is_locked
, il existe une bien meilleure solution à votre problème que celle que vous avez en tête.Vous souhaiterez peut-être utiliser atomic_flag avec l'ordre de mémoire par défaut. Il n'a pas de races de données et ne lève jamais d'exceptions comme le fait mutex avec plusieurs appels de déverrouillage (et abandonne de manière incontrôlable, je pourrais ajouter ...). Alternativement, il y a atomic (par exemple atomic [bool] ou atomic [int] (avec des crochets triangulaires, pas [])), qui a de belles fonctions comme load et compare_exchange_strong.
la source
Je veux ajouter un cas d'utilisation pour cela: cela permettrait à une fonction interne de garantir comme condition préalable / affirmation que l'appelant détient effectivement le verrou.
Pour les classes avec plusieurs de ces fonctions internes, et éventuellement de nombreuses fonctions publiques les appelant, cela pourrait garantir que quelqu'un ajoutant une autre fonction publique appelant la fonction interne a effectivement acquis le verrou.
la source