¿Ejemplo de boost shared_mutex (múltiples lecturas / una escritura)?

116

Tengo una aplicación multiproceso que tiene que leer algunos datos con frecuencia y, ocasionalmente, esos datos se actualizan. En este momento, un mutex mantiene el acceso a esos datos seguro, pero es caro porque me gustaría que varios subprocesos pudieran leer simultáneamente, y solo bloquearlos cuando se necesita una actualización (el subproceso de actualización podría esperar a que finalicen los otros subprocesos) .

Creo que esto es lo que boost::shared_mutexse supone que debe hacer, pero no tengo claro cómo usarlo y no he encontrado un ejemplo claro.

¿Alguien tiene un ejemplo simple que pueda usar para comenzar?

kevin42
fuente
El ejemplo de 1800 INFORMATION es correcto. Consulte también este artículo: Novedades de Boost Threads .
Assaf Lavie
posible duplicado de bloqueos
cHao

Respuestas:

102

Parece que harías algo como esto:

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 INFORMACIÓN
fuente
7
Esta es la primera vez que uso boost, y soy un novato en C ++, así que tal vez me falte algo, pero en mi propio código, tuve que especificar el tipo, así: boost :: shared_lock <shared_mutex> lock (_acceso);
Ken Smith
2
Estoy tratando de usar esto yo mismo, pero obtengo un error. Faltan argumentos de plantilla antes de 'bloquear'. ¿Algunas ideas?
Matt
2
@shaz Esos tienen alcance, pero puede publicarlos antes con .unlock () si lo necesita.
mmocny
4
Agregué los argumentos de la plantilla que faltan.
1
@raaj, puede obtener el bloqueo de actualización, pero la actualización a un bloqueo único se bloqueará hasta que se libere el bloqueo compartido
1800 INFORMACIÓN
166

1800 INFORMACIÓN es más o menos correcta, pero hay algunos problemas que quería corregir.

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
}

También tenga en cuenta que, a diferencia de un shared_lock, solo un hilo puede adquirir un upgrade_lock a la vez, incluso cuando no está actualizado (lo que pensé que era incómodo cuando lo encontré). Entonces, si todos sus lectores son escritores condicionales, debe encontrar otra solución.

mmocny
fuente
1
Solo para comentar sobre "otra solución". Cuando todos mis lectores eran escritores condicionales, lo que hice fue que siempre adquirieran un shared_lock, y cuando necesitaba actualizar para escribir privilegios, desbloquearía () el bloqueo del lector y adquiriría un nuevo unique_lock. Esto complicará la lógica de su aplicación, y ahora hay una ventana de oportunidad para que otros escritores cambien el estado desde que leyó por primera vez.
mmocny
8
¿No debería boost::unique_lock< boost::shared_mutex > lock(lock);leerse boost::unique_lock< boost::shared_mutex > lock( _access ); ?
SteveWilkinson
4
Esa última advertencia es muy extraña. Si solo un hilo puede contener un upgrade_lock a la vez, ¿cuál es la diferencia entre un upgrade_lock y un unique_lock?
Ken Smith
2
@Ken No estaba muy claro, pero el beneficio de upgrade_lock es que no bloquea si actualmente hay algunos shared_locks adquiridos (al menos no hasta que actualice a único). Sin embargo, el segundo hilo para intentar adquirir un upgrade_lock se bloqueará, incluso si el primero no se ha actualizado a único, lo que no esperaba.
mmocny
6
Este es un problema conocido de impulso. Parece que se resuelve en boost 1.50 beta: svn.boost.org/trac/boost/ticket/5516
Ofek Shilon
47

Desde C ++ 17 (VS2015) puede utilizar el estándar para bloqueos de lectura y escritura:

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

Para la versión anterior, puede usar boost con la misma sintaxis:

#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
fuente
5
Yo también diría typedef boost::unique_lock< Lock > WriteLock; typedef boost::shared_lock< Lock > ReadLock;.
vines
6
No es necesario incluir todo el thread.hpp. Si solo necesita las cerraduras, incluya las cerraduras. No es una implementación interna. Mantenga las inclusiones al mínimo.
Yochai Timmer
5
Definitivamente la implementación más simple, pero creo que es confuso referirse tanto a los mutex como a los bloqueos como bloqueos. Un mutex es un mutex, un bloqueo es algo que lo mantiene en un estado bloqueado.
Tim MB
17

Solo para agregar más información empírica, he estado investigando todo el problema de los bloqueos actualizables y ¿ Ejemplo para boost shared_mutex (múltiples lecturas / una escritura)? es una buena respuesta agregando la información importante de que solo un hilo puede tener un bloqueo de actualización incluso si no está actualizado, eso es importante ya que significa que no puede actualizar de un bloqueo compartido a un bloqueo único sin liberar primero el bloqueo compartido. (Esto se ha discutido en otra parte, pero el hilo más interesante está aquí http://thread.gmane.org/gmane.comp.lib.boost.devel/214394 )

Sin embargo, encontré una diferencia importante (no documentada) entre un hilo esperando una actualización a un bloqueo (es decir, necesita esperar a que todos los lectores liberen el bloqueo compartido) y un bloqueo de escritor esperando lo mismo (es decir, un bloqueo único).

  1. El hilo que está esperando un unique_lock en shared_mutex bloquea los nuevos lectores que ingresan, tienen que esperar la solicitud de los escritores. Esto asegura que los lectores no mueran de hambre a los escritores (sin embargo, creo que los escritores pueden matar de hambre a los lectores).

  2. El hilo que está esperando a que se actualice un upgradeable_lock permite que otros hilos obtengan un bloqueo compartido, por lo que este hilo podría quedar muerto si los lectores son muy frecuentes.

Este es un tema importante a considerar y probablemente debería documentarse.

Jim Morris
fuente
3
El Terekhov algorithmasegura que en 1., el escritor no puede matar de hambre a los lectores. Mira esto . Pero 2.es cierto. Un upgrade_lock no garantiza la equidad. Mira esto .
JonasVautherin
2

Utilice un semáforo con un recuento igual al número de lectores. Deje que cada lector cuente un semáforo para leer, de esa manera todos pueden leer al mismo tiempo. Luego deje que el escritor tome TODOS los recuentos de semáforos antes de escribir. Esto hace que el escritor espere a que finalicen todas las lecturas y luego bloquee las lecturas mientras escribe.

R Virzi
fuente
(1) ¿Cómo se hace que un escritor reduzca el recuento en una cantidad arbitraria de forma atómica ? (2) Si el escritor de alguna manera reduce el recuento a cero, ¿cómo espera a que los lectores que ya se están ejecutando terminen antes de escribir?
Ofek Shilon
Mala idea: si dos escritores intentan acceder simultáneamente, puede tener un punto muerto.
Caduchon
2

Gran respuesta de Jim Morris, me topé con esto y me tomó un tiempo darme cuenta. Aquí hay un código simple que muestra que después de enviar una "solicitud" para un boost de unique_lock (versión 1.54) se bloquean todas las solicitudes de shared_lock. Esto es muy interesante ya que me parece que elegir entre unique_lock y upgradeable_lock permite si queremos prioridad de escritura o no.

También (1) en la publicación de Jim Morris parece contradecir esto: Boost shared_lock. Leer preferido?

#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
fuente
De hecho, estoy teniendo problemas para averiguar por qué el código anterior se bloquea mientras el código en [ stackoverflow.com/questions/12082405/… funciona.
dale1209
1
En realidad, se bloquea en (2), no en (3), porque (2) está esperando que (1) libere su bloqueo. Recuerde: para obtener un bloqueo único, debe esperar a que finalicen todos los bloqueos compartidos existentes.
JonasVautherin
@JonesV, incluso si (2) espera a que finalicen todos los bloqueos compartidos, no sería un punto muerto porque es un hilo diferente al que adquirió (1), si la línea (3) no existiera, el programa terminar sin puntos muertos.
SagiLow