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

116

J'ai une application multithread qui doit souvent lire certaines données, et parfois ces données sont mises à jour. À l'heure actuelle, un mutex garde l'accès à ces données en toute sécurité, mais c'est cher car j'aimerais que plusieurs threads puissent lire simultanément et ne les verrouiller que lorsqu'une mise à jour est nécessaire (le thread de mise à jour pourrait attendre que les autres threads se terminent) .

Je pense que c'est ce qui boost::shared_mutexest 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?

kevin42
la source
L'exemple de 1800 INFORMATION est correct. Consultez également cet article: Nouveautés de Boost Threads .
Assaf Lavie
duplication possible des verrous
cHao

Réponses:

102

Il semble que vous feriez quelque chose comme ceci:

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
}
1800 INFORMATIONS
la source
7
C'est la première fois que j'utilise boost, et je suis un débutant en C ++, donc peut-être qu'il me manque quelque chose - mais dans mon propre code, j'ai dû spécifier le type, comme ceci: boost :: shared_lock <shared_mutex> lock (_accès);
Ken Smith
2
J'essaie de l'utiliser moi-même mais j'obtiens une erreur. arguments de modèle manquants avant 'lock'. Des idées?
Matt
2
@shaz Celles-ci sont limitées, mais vous pouvez les libérer plus tôt avec .unlock () si vous en avez besoin.
mmocny
4
J'ai ajouté les arguments de modèle manquants.
1
@raaj vous pouvez obtenir le upgrade_lock, mais la mise à niveau vers un verrou unique bloquera jusqu'à ce que le shared_lock soit libéré
1800 INFORMATIONS
166

1800 INFORMATION est plus ou moins correct, 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, 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.

mmocny
la source
1
Juste pour commenter "une autre solution". Quand tous mes lecteurs utilisaient des écrivains conditionnels, ce que je faisais, c'était qu'ils acquièrent toujours un shared_lock, et quand j'avais besoin de mettre à niveau pour écrire des privilèges, je déverrouillais () le lecteur verrouillé et j'acquis un nouveau unique_lock. Cela compliquera la logique de votre application et il y a maintenant une fenêtre d'opportunité pour les autres rédacteurs de changer l'état à partir de la première lecture.
mmocny
8
La ligne ne devrait-elle pas boost::unique_lock< boost::shared_mutex > lock(lock);lire boost::unique_lock< boost::shared_mutex > lock( _access ); ?
SteveWilkinson
4
Cette dernière mise en garde est très étrange. Si un seul thread peut contenir un upgrade_lock à la fois, quelle est la différence entre un upgrade_lock et un unique_lock?
Ken Smith du
2
@Ken Je n'ai pas été très clair, mais l'avantage de upgrade_lock est qu'il ne bloque pas s'il y a actuellement des shared_locks acquis (du moins pas jusqu'à ce que vous passiez à l'unique). Cependant, le deuxième thread pour essayer d'acquérir un upgrade_lock bloquera, même si le premier n'a pas été mis à niveau en unique, ce à quoi je ne m'attendais pas.
mmocny
6
Il s'agit d'un problème de boost connu. Il semble être résolu au boost 1.50 beta: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon
47

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;
Yochai Timmer
la source
5
Je dirais aussi typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
vignes
6
Vous n'avez pas besoin d'inclure tout le thread.hpp. Si vous avez juste besoin des serrures, incluez les serrures. Ce n'est pas une implémentation interne. Gardez les inclusions au minimum.
Yochai Timmer
5
Certainement la mise en œuvre la plus simple, mais je pense qu'il est déroutant de se référer à la fois aux mutex et aux verrous comme des verrous. Un mutex est un mutex, un verrou est quelque chose qui le maintient dans un état verrouillé.
Tim MB
17

Juste pour ajouter quelques informations empiriques, j'ai étudié toute la question des verrous évolutifs, et Exemple pour boost shared_mutex (plusieurs lectures / une écriture)? est une bonne réponse en ajoutant les informations importantes selon lesquelles un seul thread peut avoir un upgrade_lock même s'il n'est pas mis à niveau, ce qui est important car cela signifie que vous ne pouvez pas passer d'un verrou partagé à un verrou unique sans libérer le verrou partagé au préalable. (Cela a été discuté ailleurs, mais le fil le plus intéressant est 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 en attente d'une mise à niveau vers un verrou (c'est-à-dire qu'il doit attendre que tous les lecteurs libèrent le verrou partagé) et un verrou d'écriture en attente de la même chose (c'est-à-dire un unique_lock).

  1. Le thread qui attend un unique_lock sur shared_mutex bloque tout nouveau lecteur entrant, ils doivent attendre la demande des rédacteurs. Cela garantit aux lecteurs de ne pas affamer les écrivains (mais je crois que les écrivains pourraient affamer les lecteurs).

  2. Le thread qui attend un upgrade_lock pour mettre à niveau permet à d'autres threads d'obtenir un verrou partagé, donc ce thread pourrait être affamé si les lecteurs sont très fréquents.

C'est une question importante à prendre en compte et devrait probablement être documentée.

Jim Morris
la source
3
Le Terekhov algorithmgarantit que 1.l'écrivain ne peut pas affamer les lecteurs. Regarde ça . Mais 2.c'est vrai. Un upgrade_lock ne garantit pas l'équité. Regarde ça .
JonasVautherin le
2

Utilisez un sémaphore avec un nombre égal au nombre de lecteurs. Laissez chaque lecteur prendre un compte du sémaphore afin de lire, de cette façon ils peuvent tous lire en même temps. Ensuite, laissez l'écrivain prendre TOUS les comptes de sémaphore avant d'écrire. Cela oblige l'enregistreur à attendre la fin de toutes les lectures, puis à bloquer les lectures lors de l'écriture.

R Virzi
la source
(1) Comment faire pour qu'un écrivain décrémente le compte d'une quantité arbitraire de manière atomique ? (2) Si l'écrivain décrémente d'une manière ou d'une autre le décompte à zéro, comment attend-il que les lecteurs déjà en cours d'exécution se terminent avant d'écrire?
Ofek Shilon
Mauvaise idée: si deux rédacteurs tentent d'accéder simultanément, vous pouvez avoir une impasse.
Caduchon
2

Excellente réponse de Jim Morris, je suis tombé dessus et il m'a fallu un certain temps pour comprendre. Voici un code simple qui montre qu'après avoir soumis une "requête" pour un boost unique_lock (version 1.54) bloque toutes les requêtes shared_lock. Ceci est très intéressant car il me semble que le choix entre unique_lock et upgradeable_lock permet si nous voulons une priorité d'écriture ou aucune priorité.

Aussi (1) dans le message de Jim Morris semble contredire ceci: Boost shared_lock. Lire préféré?

#include <iostream>
#include <boost/thread.hpp>

using namespace std;

typedef boost::shared_mutex Lock;
typedef boost::unique_lock< Lock > UniqueLock;
typedef boost::shared_lock< Lock > SharedLock;

Lock tempLock;

void main2() {
    cout << "10" << endl;
    UniqueLock lock2(tempLock); // (2) queue for a unique lock
    cout << "11" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(1));
    lock2.unlock();
}

void main() {
    cout << "1" << endl;
    SharedLock lock1(tempLock); // (1) aquire a shared lock
    cout << "2" << endl;
    boost::thread tempThread(main2);
    cout << "3" << endl;
    boost::this_thread::sleep(boost::posix_time::seconds(3));
    cout << "4" << endl;
    SharedLock lock3(tempLock); // (3) try getting antoher shared lock, deadlock here
    cout << "5" << endl;
    lock1.unlock();
    lock3.unlock();
}
dale1209
la source
J'ai en fait du mal à comprendre pourquoi les blocages de code ci-dessus alors que le code dans [ stackoverflow.com/questions/12082405/... fonctionne.
dale1209
1
Il se bloque en fait dans (2), pas dans (3), car (2) attend que (1) libère son verrou. N'oubliez pas: pour obtenir un verrou unique, vous devez attendre la fin de tous les verrous partagés existants.
JonasVautherin le
@JonesV, même si (2) attend que tous les verrous partagés se terminent, ce ne serait pas un blocage car c'est un thread différent de celui qui a acquis (1), si la ligne (3) n'existait pas, le programme terminer sans blocages.
SagiLow