vendredi 4 mars 2011

Ais-je rêvé? Pas de boost::mutex::is_locked!

Je l'aurais pourtant juré (et j'ai eu l'air bête au boulot à cause de ça), mais contrairement à ce que je croyais et malgré ma certitude que je l'avait déjà utilisée, les {boost,std}::mutex n'ont pas de méthode is_locked qui permettrait de savoir si le mutex est acquis ou pas.

Dès que je mentionne cette fonctionnalité, l'on me regarde avec des grands yeux: "Mais pourquoi voudrais-tu faire ça? Si tu en as besoin, c'est que ton design est pourri / que tu ne sais pas utiliser les mutex / que tu est bête" (rayer la mention inutile). Alors, oui, je comprends bien que la plupart des utilisations d'une telle méthode sont des aberrations. Mais voilà mon cas d'utilisation:

Imaginez que vous avez une belle classe, gentiment thread-safe (et Wikipédia peut aller se rhabiller avec son "à fil sécurisé", non mais...), et donc possédant un beau mutex (privé, mutable, un beau mutex, quoi). Les méthodes publiques acquièrent le mutex, et appellent des méthodes privées qui s'attendent donc à ce que le mutex soit déjà pris, ce qui est donc une précondition. Afin que le prochain gus écrivant sa méthode publique dans ma classe ne casse tout, j'aimerais beaucoup pouvoir effectivement vérifier cette précondition, avec par exemple un assert(m_mutex.is_locked()). Si mon gus oublie d'acquérir le verrou, il aura une belle assertion lors de ses tests unitaires (encore faut-il qu'il écrive des tests unitaires, mais c'est un autre débat).

Et la, généralement, mon interlocuteur change d'avis: "Tiens, c'est vrai, ça serait pratique...".

Les solutions
Car elles existent.

Une possibilité est pour chaque méthode privée s'attendant à être protégée de prendre en arguments un lock (et pas un mutex), inutilisé dans le corps de la méthode. C'est une sorte de rappel, l'appelant de la fonction devant obligatoirement créer un objet lock. Bien sûr, l'on peut le contourner en créant un objet lock vide, ou qui acquiert un autre mutex, ou qui appelle unlock avant de passer l'objet, mais bon, il faut vraiment être pervers. Autre iconvénient: c'est assez moche, car ça bave dans l'interface.

Une autre possibilité est d'écrire la méthode is_locked, en tentant d'acquérir à nouveau le verrou avec un try_lock, et en vérifiant le résultat. Ce n'est pas spécialement efficace, mais dans une assertion virée des builds optimisés, ce n'est probablement pas un problème. Inconvénient: cela ne marche que si le mutex est non récursif. Sinon, c'est cassé.

Décevant.

1 commentaire:

corrector a dit…

"c'est assez moche, car ça bave dans l'interface."

Justement, le fait que le mutex soit locké est une précondition, donc fait partie de l'interface.

Avoir un paramètre lock formalise cela dans le système de type, c'est plutôt plus propre. Pour faire chic, on dit que c'est "auto-documentant".

C'est le même principe quand on utilise un auto_ptr/unique_ptr/shared_ptr comme paramètre ou type de retour d'une fonction pour effectuer (et documenter) le fait qu'il y a tranfert/abandon/partage de propriété.