web-dev-qa-db-fra.com

Pourquoi ne puis-je pas vérifier si un mutex est verrouillé?

C++ 14 semble avoir omis un mécanisme pour vérifier si un std::mutex Est verrouillé ou non. Voir cette SO question:

https://stackoverflow.com/questions/21892934/how-to-assert-if-a-stdmutex-is-locked

Il y a 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é à renvoyer 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 en plus du std::mutex d'origine.

É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 fonctionnalité plutôt que d'être un oubli.

Pourquoi?

Edit: Ok alors 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.

31
quant

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 :

Vous ne pouvez pas vraiment vérifier si un mutex est verrouillé, car une nanoseconde après la vérification, il peut être déverrouillé ou verrouillé. Donc, si vous avez écrit if (mutex_is_locked ()) … alors mutex_is_locked pourrait renvoyer le résultat correct, mais au moment où le if est exécuté, il est incorrect.

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 une 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 puis 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.

55
5gon12eder

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 complètement 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 "liste" au sens littéral, prenez-le pour signifier "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.

9
gnasher729

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 implémentation 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.

2
Peter

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 en êtes le propriétaire 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() ou try_lock() donnera 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 de try_lock vous 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).

1
Damon

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 ait effectivement acquis le verrou.

class SynchronizedClass
{

public:

void publicFunc()
{
  std::lock_guard<std::mutex>(_mutex);

  internalFuncA();
}

// A lot of code

void newPublicFunc()
{
  internalFuncA(); // whops, forgot to acquire the lock
}


private:

void internalFuncA()
{
  assert(_mutex.is_locked_by_this_thread());

  doStuffWithLockedResource();
}

};
0
B3ret

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 des fonctions Nice comme load et compare_exchange_strong.

0
Andrew