118 votes

Exemple pour boost shared_mutex (plusieurs lectures/une écriture) ?

J'ai une application multithread qui doit lire certaines données souvent, et occasionnellement ces données sont mises à jour. Actuellement, un mutex garde l'accès à ces données en toute sécurité, mais il est coûteux car je voudrais que plusieurs threads puissent lire simultanément, et les verrouiller uniquement lorsqu'une mise à jour est nécessaire (le thread de mise à jour pourrait attendre que les autres threads aient terminé).

Je pense que c'est ce que boost::shared_mutex est censé faire, mais je ne sais pas comment l'utiliser et je n'ai pas trouvé d'exemple clair.

Quelqu'un a-t-il un exemple simple que je pourrais utiliser pour commencer ?

0 votes

L'exemple de 1800 INFORMATION est correct. Voir aussi cet article : Quoi de neuf dans Boost Threads ? .

0 votes

Duplicata possible de Verrous de lecteur/écriture en C++

174voto

mmocny Points 3655

1800 INFORMATIONS est plus ou moins correcte, mais il y a quelques problèmes que je voulais corriger.

boost::shared_mutex _access;
void reader()
{
  boost::shared_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access
}

void conditional_writer()
{
  boost::upgrade_lock< boost::shared_mutex > lock(_access);
  // do work here, without anyone having exclusive access

  if (something) {
    boost::upgrade_to_unique_lock< boost::shared_mutex > uniqueLock(lock);
    // do work here, but now you have exclusive access
  }

  // do more work here, without anyone having exclusive access
}

void unconditional_writer()
{
  boost::unique_lock< boost::shared_mutex > lock(_access);
  // do work here, with exclusive access
}

Notez également que, contrairement à un shared_lock, un seul thread peut acquérir un upgrade_lock à la fois, même s'il n'est pas mis à niveau (ce que j'ai trouvé gênant lorsque je l'ai rencontré). Donc, si tous vos lecteurs sont des écrivains conditionnels, vous devez trouver une autre solution.

1 votes

Juste un commentaire sur "une autre solution". Lorsque tous mes lecteurs étaient des écrivains conditionnels, je faisais en sorte qu'ils acquièrent toujours un shared_lock, et lorsque j'avais besoin de passer aux privilèges d'écriture, je devais .unlock() le verrou du lecteur et acquérir un nouveau unique_lock. Cela va compliquer la logique de votre application, et il y a maintenant une fenêtre d'opportunité pour les autres écrivains de changer l'état de la première lecture.

8 votes

La ligne ne devrait-elle pas boost::unique_lock< boost::shared_mutex > lock(lock); lire boost::unique_lock< boost::shared_mutex > lock( _access ); ?

0 votes

Je ne pense pas. Constructeur : explicit upgrade_to_unique_lock(upgrade_lock<Lockable>& m_) ;

104voto

1800 INFORMATION Points 55907

On dirait que vous feriez quelque chose comme ça :

boost::shared_mutex _access;
void reader()
{
  // get shared access
  boost::shared_lock<boost::shared_mutex> lock(_access);

  // now we have shared access
}

void writer()
{
  // get upgradable access
  boost::upgrade_lock<boost::shared_mutex> lock(_access);

  // get exclusive access
  boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
  // now we have exclusive access
}

7 votes

C'est la première fois que j'utilise boost, et je suis un débutant en C++, donc peut-être qu'il y a quelque chose qui m'échappe -- mais dans mon propre code, je devais spécifier le type, comme ceci : boost::shared_lock<shared_mutex> lock(_access) ;

2 votes

J'essaie de l'utiliser moi-même, mais j'obtiens une erreur : il manque des arguments de modèle avant 'lock'. Avez-vous une idée ?

0 votes

Bonjour, est-ce que ces verrous ont une portée ou dois-je les libérer explicitement ? TIA

50voto

Yochai Timmer Points 19802

Depuis C++ 17 (VS2015), vous pouvez utiliser la norme pour les verrous en lecture-écriture :

#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, vous pouvez utiliser boost avec la même syntaxe :

#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;

5 votes

Je dirais aussi typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock; .

6 votes

Vous n'avez pas besoin d'inclure tout le thread.hpp . Si vous avez seulement besoin des verrous, incluez les verrous. Il ne s'agit pas d'une implémentation interne. Gardez les includes au minimum.

5 votes

C'est certainement l'implémentation la plus simple, mais je pense qu'il est confus de se référer à la fois aux mutex et aux verrous en tant que verrous. Un mutex est un mutex, un verrou est quelque chose qui le maintient dans un état verrouillé.

18voto

Jim Morris Points 1512

Pour ajouter des informations plus empiriques, j'ai étudié la question des serrures évolutives et Exemple pour boost shared_mutex (plusieurs lectures/une écriture) ? est une bonne réponse qui ajoute l'information importante qu'un seul thread peut avoir un upgrade_lock même s'il n'est pas mis à jour, ce qui est important car cela signifie que vous ne pouvez pas passer d'un verrou partagé à un verrou unique sans libérer d'abord le verrou partagé. (Ce sujet a été abordé ailleurs, mais le fil de discussion le plus intéressant se trouve ici. http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Cependant, j'ai trouvé une différence importante (non documentée) entre un thread qui attend une mise à jour d'un verrou (c'est-à-dire qui doit attendre que tous les lecteurs libèrent le verrou partagé) et un verrou d'écrivain qui attend la même chose (c'est-à-dire un unique_lock).

  1. Le thread qui attend un unique_lock sur le shared_mutex bloque l'arrivée de tout nouveau lecteur, qui doit attendre la requête de l'écrivain. Cela permet aux lecteurs de ne pas affamer les écrivains (cependant, je crois que les écrivains pourraient affamer les lecteurs).

  2. Le thread qui attend la mise à niveau d'un upgradeable_lock permet aux autres threads d'obtenir un verrou partagé, de sorte que ce thread pourrait être affamé si les lecteurs sont très fréquents.

Il s'agit d'une question importante à prendre en compte, qui devrait probablement être documentée.

3 votes

El Terekhov algorithm garantit qu'en 1. l'écrivain ne peut pas affamer les lecteurs. Voir ce . Mais 2. est vrai. Un verrou de mise à niveau ne garantit pas l'équité. Voir ce .

2voto

R Virzi Points 31

Utilisez un sémaphore dont le nombre est égal au nombre de lecteurs. Laissez chaque lecteur prendre un compte du sémaphore pour lire, de cette façon ils peuvent tous lire en même temps. Ensuite, laissez l'écrivain prendre TOUS les comptes du sémaphore avant d'écrire. Ainsi, le rédacteur attend que toutes les lectures soient terminées et bloque les lectures pendant l'écriture.

0 votes

(1) Comment faire pour qu'un écrivain décrémente le compte d'une quantité arbitraire ? atomiquement ? (2) Si le rédacteur décrémente d'une manière ou d'une autre le compte à zéro, comment attend-il que les lecteurs déjà en cours d'exécution aient terminé avant d'écrire ?

0 votes

Mauvaise idée : si deux rédacteurs essaient d'accéder simultanément, il peut y avoir un blocage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X