Je recherche un bon verrou de lecture/écriture en C++. Nous avons un cas d'utilisation d'un seul écrivain peu fréquent et de nombreux lecteurs fréquents et nous aimerions optimiser pour cela. De préférence, je voudrais une solution multiplateforme, mais un seul Windows serait acceptable.
Les versions plus récentes de boost :: thread ont des verrous en lecture/écriture (1.35.0 et versions ultérieures, apparemment les versions précédentes ne fonctionnaient pas correctement).
Ils ont les noms shared_lock
, unique_lock
et upgrade_lock
et opérez sur un shared_mutex
.
Utiliser des choses pré-testées et pré-construites standard est toujours bon (par exemple, Boost comme une autre réponse l'a suggéré), mais c'est quelque chose qui n'est pas trop difficile à construire vous-même. Voici une petite mise en œuvre stupide tirée d'un de mes projets:
#include <pthread.h>
struct rwlock {
pthread_mutex_t lock;
pthread_cond_t read, write;
unsigned readers, writers, read_waiters, write_waiters;
};
void reader_lock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
if (self->writers || self->write_waiters) {
self->read_waiters++;
do pthread_cond_wait(&self->read, &self->lock);
while (self->writers || self->write_waiters);
self->read_waiters--;
}
self->readers++;
pthread_mutex_unlock(&self->lock);
}
void reader_unlock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
self->readers--;
if (self->write_waiters)
pthread_cond_signal(&self->write);
pthread_mutex_unlock(&self->lock);
}
void writer_lock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
if (self->readers || self->writers) {
self->write_waiters++;
do pthread_cond_wait(&self->write, &self->lock);
while (self->readers || self->writers);
self->write_waiters--;
}
self->writers = 1;
pthread_mutex_unlock(&self->lock);
}
void writer_unlock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
self->writers = 0;
if (self->write_waiters)
pthread_cond_signal(&self->write);
else if (self->read_waiters)
pthread_cond_broadcast(&self->read);
pthread_mutex_unlock(&self->lock);
}
void rwlock_init(struct rwlock *self) {
self->readers = self->writers = self->read_waiters = self->write_waiters = 0;
pthread_mutex_init(&self->lock, NULL);
pthread_cond_init(&self->read, NULL);
pthread_cond_init(&self->write, NULL);
}
pthreads
n'étant pas vraiment natif de Windows, mais l'idée générale est là. Cette implémentation est légèrement biaisée en faveur des écrivains (une horde d'écrivains peut affamer les lecteurs indéfiniment); modifiez simplement writer_unlock
si vous préférez que l'équilibre soit l'inverse.
Oui, c'est C et non C++. La traduction est un exercice laissé au lecteur.
Greg Rogers a souligné que la norme POSIX spécifie pthread_rwlock_*
. Cela n'aide pas si vous n'avez pas pthreads
, mais cela m'a remué l'esprit de me souvenir: Pthreads-w32 devrait fonctionner! Au lieu de porter ce code sur non -pthreads
pour votre propre usage, utilisez simplement Pthreads-w32 sous Windows et natif pthreads
partout ailleurs.
Depuis C++ 17 (VS2015), vous pouvez utiliser la norme:
#include <shared_mutex>
typedef std::shared_mutex Lock;
typedef std::unique_lock< Lock > WriteLock;
typedef std::shared_lock< Lock > ReadLock;
Lock myLock;
void ReadFunction()
{
ReadLock r_lock(myLock);
//Do reader stuff
}
void WriteFunction()
{
WriteLock w_lock(myLock);
//Do writer stuff
}
Pour les anciennes versions et normes du compilateur, vous pouvez utiliser boost pour créer un verrou en lecture-écriture:
#include <boost/thread/locks.hpp>
#include <boost/thread/shared_mutex.hpp>
typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > WriteLock;
typedef boost::shared_lock< Lock > ReadLock;
Quoi que vous décidiez d'utiliser, comparez votre charge de travail à de simples verrous, car les verrous en lecture/écriture ont tendance à être 3 à 40 fois plus lents que les simples mutex, lorsqu'il n'y a pas de conflit.
Voici ne référence
Modifier: le lien MSDN Magazine n'est plus disponible. L'article CodeProject est maintenant disponible sur https://www.codeproject.com/Articles/32685/Testing-reader-writer-locks et le résume assez bien. J'ai également trouvé un nouveau lien MSDN sur Compound Synchronization Objects .
Il y a un article sur les verrous de lecture/écriture sur MSDN qui en présente quelques implémentations. Il présente également le verrou Slim reader/writer, une primitive de synchronisation du noyau introduite avec Vista. Il y a aussi un article CodeProject sur la comparaison des différentes implémentations (y compris celles de l'article MSDN).
C++ 17 prend en charge std::shared_mutex
. Il est pris en charge dans MSVC++ 2015 et 2017.
Intel Thread Building Blocks propose également quelques variantes de rw_lock:
http://www.threadingbuildingblocks.org/
Ils ont un spin_rw_mutex pour de très courtes périodes de conflit et un queueing_rw_mutex pour des périodes de conflit plus longues. Le premier peut être utilisé dans un code particulièrement sensible aux performances. Ce dernier est plus comparable en performances à celui fourni par Boost.Thread ou directement à l'aide de pthreads. Mais profil pour vous assurer que celui-ci est gagnant pour vos modèles d'accès.
Je peux recommander bibliothèque ACE , qui fournit une multitude de mécanismes de verrouillage et est porté sur diverses plates-formes.
Selon les conditions aux limites de votre problème, les classes suivantes peuvent être utiles:
ACE_RW_Process_Mutex
ACE_Write_Guard
et ACE_Read_Guard
ACE_Condition
Boost.Thread a depuis la version 1.35.0 prend déjà en charge les verrous lecteur-écrivain. La bonne chose à ce sujet est que l'implémentation est grandement multiplateforme, évaluée par les pairs et est en fait ne implémentation de référence pour la prochaine norme C++ 0x .
http://www.codeproject.com/KB/threads/ReaderWriterLock.aspx
Voici une implémentation bonne et légère adaptée à la plupart des tâches.
Classe de verrouillage de synchronisation à plusieurs lecteurs et à un seul écrivain pour Win32 par Glenn Slayde
Vous pouvez copier l'excellent ReentrantReadWriteLock de Sun . Il comprend des fonctionnalités telles que l'équité en option, la rétrogradation des verrous et bien sûr la réentrance.
Oui, c'est en Java, mais vous pouvez facilement le lire et le transposer en C++, même si vous ne connaissez pas Java. La documentation à laquelle j'ai lié contient toutes les propriétés comportementales de cette implémentation afin que vous puissiez vous assurer qu'elle fait ce que vous voulez.
Si rien d'autre, c'est un guide.