Disons que nous voulons faire de la classe A code> Safe-Safe à l'aide d'un
std :: mutex code>. De la même manière que mon constructeur de copie et mon opérateur d'affectation de copie au code ci-dessous:
template<typename T>
class A {
private:
T t;
mutable std::mutex mtx;
public:
A() : t(), mtx() { }
A(const A& other) : t(), mtx()
{
std::lock_guard<std::mutex> _lock(other.mtx);
t = other.t;
}
A& operator=(const A& other)
{
if (this!=&other) {
A tmp(other);
std::lock_guard<std::mutex> _lock(mtx);
std::swap(t, tmp.t);
}
return *this;
}
T get() const
{
std::lock_guard<std::mutex> _lock(mtx);
return t;
}
};
5 Réponses :
Je ne suis pas une autorité à ce sujet, car la multi-threading est délicate, mais cela a l'air bien jusqu'à présent em>.
BTW, vous vouliez probablement dire A(const A& other) : mtx()
{
std::lock_guard<std::mutex> _lock(other.mtx);
i = other.i;
}
Je suis désolé, j'ai corrigé le Lock_Guard code> s. Je crois que pour être dans un état cohérent, je pense que le moyen le plus sûr est de verrouiller l'objet entier plutôt que d'utiliser des getters à fil.
Sûr. Mais si par exemple Un getter à fil de thread serait suffisant pour une certaine classe que vous pouvez éviter la complexité supplémentaire (en termes de quoi-chose peut-être).
Vous ne le voyez pas vraiment parce que les installations de threading standard sont extrêmement nouvelles et je ne connais pas d'un seul compilateur qui les prend en charge - vous obtiendriez plus loin à la recherche d'exemples de filetage. En outre, votre utilisation gratuite de synchronisation conduira probablement à une mauvaise performance, mais c'est juste mon opinion. P>
Oui, Boost le fait aussi - je viens de l'écrire dans C ++ 0x Installations fournies par certains compilateurs déjà (GCC 4,3+, MSVC 10 et quelques autres). Cela conduira à des performances moins que optimales, mais c'est la seule façon de ne pas avoir de structure de données sans verrouillage.
@Papadop: MSVC10 ne prend pas en charge les installations de threading standard. Ils ont roulé leur propre (le conct) pour cette libération. La performance que je voulais dire est que le filetage d'une application doit être conçu à portée de main. Et si quelqu'un qui souhaite utiliser votre objet n'est pas multi-fileté, sinon il est inutile? Le fait est que vous, en tant que fournisseur d'objet, ne vous entendez aucune idée lorsque, où, ou même si l'utilisateur de l'objet nécessite une synchronisation à moins que le filetage soit une partie explicite du but de l'objet.
Il est utilisé dans mon propre code multithread. Vous avez raison sur MSVC - j'ai oublié que j'ai changé pour utiliser Intel Compiler + TBB sur Windows qu'ils offrent tout cela. Je peux toujours désactiver le mutex en passant un null_mutex qui a Null_Mutex :: Lock () et Null_Mutex :: Débloquer () qui ne fait rien.
Ceci est plus correct, mais pas entièrement robuste:
Je ne souhaite pas utiliser la fonction get () - principalement parce que ce n'est pas assez générique; Que se passe-t-il si j'ai plusieurs valeurs (regardez aussi à ma note)? Vous faites également deux erreurs: votre version serait une impasse dans une affaire d'auto-affectation et mutex.lock () peut lancer une exception dans votre destructeur. Enfin, je déclare mutex aussi mutable, le Cons ne crée aucun problème.
@IPAPDOP REIT: Vous pouvez toujours initialiser correctement avec une acquisition si vous créez un objet à transmettre comme argument. Readlocks: Il est bon de créer un deuxième paramètre de modèle pour le type de verrou que vous utiliserez ou des classes de base verrouillables. Les sous-classes avec des serrures sont souvent idéales car vous préférez protéger l'état vulnérable de l'objet, plutôt que les membres dans des étendues restreintes. Tentative d'assurer la sécurité du thread à l'aide d'un mutex de base pour un t code> est trop souvent fastidieux et sujet aux erreurs.
(suite) Une interface singulière (comme modèle générique) est très susceptible d'être des blocages (dont certains seront intensément difficiles à reproduire). Votre exemple est trivial par rapport à l'utilisation du monde réel; Un mutex récursif est une serrure d'objet généralisée plus pratique. Il est également préférable d'écrire / d'utiliser quelques objets supplémentaires pour transmettre et appliquer vos intentions à chaque étape de la mise en œuvre. L'utilisation de mutable code> ne vous sauvegardera que de la réécriture du code existant aujourd'hui. Si la sécurité du thread et la correction du programme est votre principale préoccupation, je vous encourage à l'omettre.
En règle générale, j'évite les serrures récursifs comme imo, ils ne font que masquer une mauvaise utilisation des serrures. Oui c'est un exemple trivial, mais je ne pense pas que je devrais poster tout ce que j'ai très similaire à ce que j'ai donc 1) je ne peux pas utiliser les opérations atomiques et 2) il doit être conforme à des pratiques communes où obtenir () est un const. Je ne vois pas pourquoi mutable est une telle mauvaise pratique, car elle n'a rien à voir avec l'état interne de l'objet - il est là pour permettre au MuTex de faire respecter la sécurité de thread-sécurité.
Ignorer tous les détails de la mise en œuvre, la raison pour laquelle vous ne voyez pas ce modèle est parce que c'est très probablement em> que vous vous bloquez sur le mauvais niveau d'abstraction. P>
Obtenir que le code multithreadé correct n'est pas simplement une question de ne rien ne rien faire "Crashes" et des objets célibataires restent dans un état cohérent. Et si vous (pensez) besoin du schéma ci-dessus, vous pouvez penser que vous êtes épargné lorsque votre application est toujours en train de faire - la mauvaise chose. P>
Quant aux détails de la mise en œuvre: Depuis que vous utilisez déjà C ++ 0x pour cela, vous devez également mettre en œuvre des opérations de déplacement définies correctement définies. P>
obtenez () code> fonction, Ensuite, la lecture de l'objet peut / entraînera des données incohérentes. Li>
ul>
Je ne me soucie pas particulièrement de la vie - il s'agit d'un problème différent, totalement orthogonal d'avoir un objet de sécurité. J'utilise le motif ci-dessus pour protéger l'accès aux ressources partagées qui ne sont pas thread-sûres et de manière intrinsèquement non évolutive (donc je ne me soucie pas de la performance et des ressources partagées survient aux threads). La mauvaise chose ici est d'avoir un état incohérent interne, de ne pas avoir de valeurs différentes. Merci pour le rappel sur les opérations de déménagement cependant.
Je me demande quelles ressources spéciales que vous avez ici, qui sont non évolutives i>, survivre aux threads i> et doivent encore être copiés? Ou avez-vous des objets "emballage" à vos ressources et avez-vous besoin de les copier?
Utilisation simple (pas la mienne, mais vous obtenez l'idée): une file d'attente qui threads met des objets dedans. De temps en temps, un thread vient et copie cette file d'attente (ne le clut pas) et la verse dans un fichier. Les autres threads continuent d'utiliser la file d'attente initiale et continuent de le remplir. Vous avez maintenant un système d'instantané.
Quiet Question, nouvelle réponse:
IMHO, une meilleure façon de traiter le problème de verrouillage mort de l'opérateur d'affectation de copie d'origine est le suivant: P>
#include <mutex> #include <shared_mutex> class A { private: int i; mutable std::shared_mutex mtx; public: A() : i(), mtx() { } A(const A& other) : i(), mtx() { std::shared_lock<std::shared_mutex> _lock(other.mtx); i = other.i; } A& operator=(const A& other) { if (this!=&other) { std::unique_lock<std::shared_mutex> _mylock(mtx, std::defer_lock); std::shared_lock<std::shared_mutex> _otherlock(other.mtx, std::defer_lock); std::lock(_mylock, _otherlock); i = other.i; } return *this; } int get() const { std::shared_lock<std::shared_mutex> _mylock(mtx); return i; } };
Si l'on utilisait l'affectation par valeur a & opérateur = (un autre) code> serait la nécessité d'un MuTex partagé i> être évité? (Seul un mutex normal serait nécessaire).
Dans les deux cas, seul un mutex normal est nécessaire. La question est la suivante: le code peut-il fonctionner plus rapidement si vous autorisez plusieurs lecteurs des données protégées à lire en même temps? Dans le cas de a & opérateur = (un autre) code>, cela implique toujours de la copie de la construction d'un
A code> si vous souhaitez vous attribuer. Si le constructeur de copie utilise un mutex exclusif, il ne peut s'agir que de la copie construite d'un fil à la fois. S'il utilise un mutex partagé avec un verrou de lecture, il peut s'agir de la copie construite à partir de nombreux threads à la fois. Cela ne signifie pas que le mutex partagé est toujours plus rapide. Les mutiles partagés peuvent avoir une surcharge supérieure.
Mais vous montrez une solution avec un nombre partagé_mutex. Non? AMYWAY, j'ai besoin d'étudier cela plus pour comprendre les subtilités. Ici, c'est un autre défi associé Stackoverflow.com/q/60087792/225186 si vous êtes intéressé
Les problèmes de sécurité du fil dans un opérateur d'affectation pourraient être un symptôme d'autres choses qui ne vont pas dans votre application. Par exemple, si des threads multiples attribuent simultanément à la même manière simultanée, la fabrication
Opérateur = code> Safe avec un mutex empêchera les incohérences de données, mais conduira à votre contenu étant mis au rebut silencieusement / écrasé ...
Que voulez-vous dire jeté / écrasé? Mon cas d'utilisation est que je souhaite copier construire ou assigner à mon objet et être dans un état cohérent. Je ne m'attends pas à ce que tous les copies soient identiques, mais je m'attends à avoir un état cohérent interne.
@ André Caron: Mais ce serait le comportement attendu. Si j'ai 5 threads attribuant à l'objet, la valeur attribuée par la dernière personne à faire l'affiliation est ce que j'attends dans l'objet. Ce que les autres ont fait est sans importance. La cohérence des données est la seule chose b> que je suis inquiet.
@Papadop: L'utilisation de la copie et d'échange d'échange d'idiome dans votre affectation devrait aider à réduire la complexité de l'opérateur d'affectation. Et également supprimer le problème actuel de Deadlock B>. Fil 1: x = y; Fil 2: y = x; Cette situation a la possibilité d'une impasse.
@Martin York: C'est pourquoi je dis que je le dis pourrait i> un symptôme de quelque chose d'autre qui ne va pas. La plupart des scénarios multi-threadings que j'ai rencontrés sur des valeurs cumulées produites par différents threads (c'est-à-dire un fichier journal partagé, producteur / consommateur, etc.). Bien entendu, la cohérence des données est toujours une nécessité, mais il y a peu de cas où vous ne voulez que la dernière valeur.
@ André Caron: Si vous voulez plus d'une valeur, vous feriez mieux de les stocker dans des objets de différence.
@Martin York: Merci d'avoir souligné l'impasse possible. Voir le code mis à jour. @ André Caron: Mon cas d'utilisation permet d'écraser les valeurs. Mais vous devez écraser tous tous, ou aucun d'entre eux.