Affectation par copie-échange contre deux verrous

Empruntant l’exemple de Howard Hinnant et le modifiant pour qu’il utilise le copier- déplacer , est-ce op = thread-safe?

struct A { A() = default; A(A const &x); // Assume implements correct locking and copying. A& operator=(A x) { std::lock_guard lock_data (_mut); using std::swap; swap(_data, x._data); return *this; } private: mutable std::mutex _mut; std::vector _data; }; 

Je crois que ce thread-safe (rappelez-vous que le paramètre de op = est passé par valeur) et que le seul problème que je puisse trouver est celui qui a été balayé sous le tapis: le copieur ctor. Cependant, il s’agirait d’une classe rare qui permet l’atsortingbution de copies mais pas la construction de copies, de sorte que le problème existe également dans les deux alternatives.

Étant donné que l’auto-affectation est si rare (du moins pour cet exemple) que je ne crains pas une copie supplémentaire si cela se produit, considérez que l’optimisation potentielle de this! = & Rhs soit négligeable ou pessimisée. Y aurait-il une autre raison de le préférer ou de l’éviter par rapport à la stratégie initiale (ci-dessous)?

 A& operator=(A const &rhs) { if (this != &rhs) { std::unique_lock lhs_lock( _mut, std::defer_lock); std::unique_lock rhs_lock(rhs._mut, std::defer_lock); std::lock(lhs_lock, rhs_lock); _data = rhs._data; } return *this; } 

Incidemment, je pense que cela résume succinctement le copieur, du moins pour cette classe, même si elle est un peu obtuse:

 A(A const &x) : _data {(std::lock_guard(x._mut), x._data)} {} 

Je crois que votre travail est thread-safe (en supposant bien sûr pas de références en dehors de la classe). La performance de celui-ci par rapport à const A& variant dépend probablement de A. Je pense que pour de nombreux A, votre réécriture sera aussi rapide, sinon plus rapide. Le grand contre-exemple que j’ai est std :: vector (et les classes similaires).

std :: vector a une capacité qui ne participe pas à sa valeur. Et si le lhs a une capacité suffisante par rapport au rhs, alors le réutiliser, au lieu de le jeter à un temp, peut être un gain de performance.

Par exemple:

 std::vector v1(5); std::vector v2(4); ... v1 = v2; 

Dans l’exemple ci-dessus, si v1 conserve sa capacité à effectuer l’affectation, l’affectation peut être effectuée sans allocation de stack ni désaffectation. Mais si vector utilise l’idiome de swap, il effectue une allocation et une désallocation.

Je remarque que pour ce qui est de la sécurité des threads, les deux algorithmes verrouillent / déverrouillent deux verrous. Bien que la variante d’échange évite la nécessité de verrouiller les deux en même temps. Je pense qu’en moyenne, le coût pour verrouiller les deux en même temps est faible. Mais dans les cas d’utilisation très contestés, cela pourrait devenir une préoccupation.