J'ai une application multithread qui doit lire certaines données souvent, et parfois ces données sont mises à jour. À l'heure actuelle, un mutex sécurise l'accès à ces données, mais cela coûte cher 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 peut attendre la fin des autres threads) .
Je pense que c'est ce que boost::shared_mutex
est censé le faire, mais je ne comprends pas comment l'utiliser et je n'ai pas trouvé d'exemple clair.
Quelqu'un at-il un exemple simple que je pourrais utiliser pour commencer?
Il semblerait que vous fassiez 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 INFORMATION est plus ou moins correct, mais je voulais corriger quelques points.
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 en même temps, même s'il n'est pas mis à niveau (ce que je pensais être gênant lorsque je l'ai rencontré). Donc, si tous vos lecteurs sont des écrivains conditionnels, vous devez trouver une autre solution.
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 versions plus anciennes, 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;
Juste pour ajouter des informations plus empiriques, j’ai enquêté sur toute la question des verrous évolutifs, et Exemple pour boost shared_mutex (plusieurs lectures/une écriture)? est une bonne réponse en ajoutant l’information importante qu’un seul Le 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 avoir préalablement relâché le verrou partagé. (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 fil en attente d'une mise à niveau vers un verrou (c'est-à-dire qu'il faut attendre que tous les lecteurs libèrent le verrou partagé) et un verrou pour rédacteur en attente de la même chose (c'est-à-dire un unique_lock).
Le thread qui attend un unique_lock sur shared_mutex bloque tout nouveau lecteur entrant, il doit attendre la demande de l'auteur. Cela garantit que les lecteurs ne pas affamer les écrivains (cependant, je crois que les écrivains pourraient affamer les lecteurs).
Le thread en attente d'un upgradeable_lock permet aux autres threads d'obtenir un verrou partagé. Ce thread peut donc être affamé si les lecteurs sont très fréquents.
C'est une question importante à considérer et qui devrait probablement être documentée.
Utilisez un sémaphore avec un nombre égal au nombre de lecteurs. Laisser chaque lecteur compter le sémaphore pour lire, afin qu’il puisse tous lire en même temps. Ensuite, laissez l’écrivain prendre TOUS les comptes de sémaphore avant d’écrire. Cela oblige le rédacteur à attendre que toutes les lectures se terminent, puis à bloquer les lectures pendant l'écriture.
Excellente réponse de Jim Morris, je suis tombé sur cela et il m'a fallu un certain temps pour comprendre. Voici un code simple qui montre qu'après avoir soumis une "demande" pour un boost unique_lock (version 1.54), toutes les requêtes shared_lock sont bloquées. Ceci est très intéressant car il me semble que choisir entre unique_lock et upgradable_lock le permet si nous voulons une priorité en écriture ou aucune priorité.
Aussi (1) dans le post 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();
}