J'ai un grand nombre d'objets potentiellement écrits simultanément. Je veux protéger cet accès avec des mutex. À cette fin, j'ai pensé utiliser un std::vector<std::mutex>
, mais cela ne fonctionne pas, car std::mutex
n'a pas de constructeur de copie ou de déplacement, alors que std::vector::resize()
l'exige.
Quelle est la solution recommandée à cette énigme?
edit : Tous les conteneurs à accès aléatoire C++ nécessitent-ils une copie ou un déplacement des constructeurs pour le redimensionnement? Est-ce que std :: deque pourrait vous aider?
éditer à nouveau
Tout d'abord, merci pour toutes vos pensées. Je ne suis pas intéressé par les solutions qui évitent les mutices et/ou les déplacent dans les objets (je m'abstiens de donner des détails/raisons). Donc, étant donné le problème que je veux un nombre ajustable de mutices (où l'ajustement est garanti lorsque aucun mutex n'est verrouillé), il semble y avoir plusieurs solutions.
1 Je pourrais utiliser un nombre fixe de mutices et utiliser une fonction de hachage pour mapper des objets sur des mutices (comme dans la réponse du capitaine Oblivous). Cela entraînera des collisions, mais le nombre de collisions doit être faible si le nombre de mutices est beaucoup plus grand que le nombre de threads, mais reste inférieur au nombre d'objets.
2 Je pourrais définir une classe wrapper (comme dans la réponse de ComicSansMS), par exemple.
struct mutex_wrapper : std::mutex
{
mutex_wrapper() = default;
mutex_wrapper(mutex_wrapper const&) noexcept : std::mutex() {}
bool operator==(mutex_wrapper const&other) noexcept { return this==&other; }
};
et utilisez un std::vector<mutex_wrapper>
.
3 Je pourrais utiliser std::unique_ptr<std::mutex>
pour gérer des mutex individuels (comme dans la réponse de Matthias). Le problème avec cette approche est que chaque mutex est alloué et désalloué individuellement sur le tas. Donc je préfère
4 std::unique_ptr<std::mutex[]> mutices( new std::mutex[n_mutex] );
quand un certain nombre n_mutex
de mutices est alloué initialement. Si ce nombre était jugé insuffisant par la suite, je me contenterais
if(need_mutex > n_mutex) {
mutices.reset( new std::mutex[need_mutex] );
n_mutex = need_mutex;
}
Alors, lequel de ceux-ci (1,2,4) devrais-je utiliser?
vector
exige que les valeurs soient déplaçables, afin de conserver un tableau contigu de valeurs au fur et à mesure de sa croissance. Vous pouvez créer un vecteur contenant des mutex, mais vous ne pouvez rien faire qui puisse nécessiter de le redimensionner.
Les autres conteneurs ne sont pas soumis à cette exigence. deque
ou [forward_]list
devrait fonctionner, tant que vous construisez les mutex en place pendant la construction ou en utilisant emplace()
ou resize()
Des fonctions telles que insert()
et Push_back()
ne fonctionneront pas.
Vous pouvez également ajouter un niveau supplémentaire d'indirection et stocker unique_ptr
; mais votre commentaire dans une autre réponse indique que vous estimez que le coût supplémentaire de l'allocation dynamique est inacceptable.
Vous pouvez utiliser std::unique_ptr<std::mutex>
au lieu de std::mutex
. unique_ptr
s sont mobiles.
Si vous voulez créer une certaine longueur:
std::vector<std::mutex> mutexes;
...
size_t count = 4;
std::vector<std::mutex> list(count);
mutexes.swap(list);
Je suggère d'utiliser un pool de mutex fixe. Conservez un tableau fixe de std::mutex
et sélectionnez le tableau à verrouiller en fonction de l'adresse de l'objet, comme vous le feriez avec une table de hachage.
std::array<std::mutex, 32> mutexes;
std::mutex &m = mutexes[hashof(objectPtr) % mutexes.size()];
m.lock();
La fonction hashof
pourrait être quelque chose de simple qui décale la valeur du pointeur de quelques bits. De cette façon, il vous suffit d'initialiser les mutex une seule fois et vous évitez la copie du redimensionnement du vecteur.
Si l’efficacité pose un tel problème, je suppose que vous n’avez que de très petites structures de données qui sont modifiées très souvent. Il est alors probablement préférable d’utiliser Atomic Compare And Swap (et d’autres opérations atomiques) au lieu d’utiliser des mutex, en particulier std::atomic_compare_exchange_strong
.
Pourquoi ne pas déclarer chaque mutex en tant que pointeur?
std::vector<std::mutex *> my_mutexes(10)
//Initialize mutexes
for(int i=0;i<10;++i) my_mutexes[i] = new std::mutex();
//Release mutexes
for(int i=0;i<10;++i) delete my_mutexes[i];