2012-08-22 8 views
8

Estaba revisando la biblioteca de impulso (versión 1.45) para un bloqueo de lector/escritor. Cuando ejecuté mis pruebas en él, parecía que shared_ptr prefería mis hilos de lectura, es decir, cuando mi escritor intentó tomar el bloqueo para su operación, no impidió que ocurriera ninguna lectura posterior.Boost shared_lock. Leer preferido?

¿Es posible en el impulso para cambiar este comportamiento?

using namespace std; 
using namespace boost; 

mutex outLock; 
shared_mutex workerAccess; 
bool shouldIWork = true; 

class WorkerKiller 
{ 
public: 

    void operator()() 
    { 
     upgrade_lock<shared_mutex> lock(workerAccess); 
     upgrade_to_unique_lock<shared_mutex> uniqueLock(lock); 

     cout << "Grabbed exclusive lock, killing system" << endl; 
     sleep(2); 
     shouldIWork = false; 
     cout << "KILLING ALL WORK" << endl; 
    } 

private: 
}; 

class Worker 
{ 
public: 

    Worker() 
    { 
    } 

    void operator()() 
    { 
     shared_lock<shared_mutex> lock(workerAccess); 

     if (!shouldIWork) { 
      outLock.lock(); 
      cout << "Workers are on strike. This worker refuses to work" << endl; 
      outLock.unlock(); 
     } else { 
      sleep(1); 

      outLock.lock(); 
      cout << "Worked finished her work" << endl; 
      outLock.unlock(); 
     } 
    } 
}; 

int main(int argc, char* argv[]) 
{ 
    Worker w1; 
    Worker w2; 
    Worker w3; 
    Worker w4; 
    WorkerKiller wk; 

    boost::thread workerThread1(w1); 
    boost::thread workerThread2(w2); 

    boost::thread workerKillerThread(wk); 

    boost::thread workerThread3(w3); 
    boost::thread workerThread4(w4); 

    workerThread1.join(); 
    workerThread2.join(); 
    workerKillerThread.join(); 
    workerThread3.join(); 

    return 0; 
} 

Y aquí es la salida cada vez que:

trabajado terminado su trabajo
trabajado terminado su trabajo
trabajado terminado su trabajo
trabajado terminado su trabajo
Grabbed bloqueo exclusivo, matando sistema
KILLING ALL WORK

Mi Requisito

Si el escritor trató de agarrar un bloqueo exclusivo, me gustaría que todas las operaciones de lectura anterior hasta terminar. Y luego todas las operaciones de lectura subsecuentes para bloquear.

+2

Es posible que desee publicar una parte de código de prueba - mis habilidades psíquicas de depuración son débiles :-) – Bukes

Respuesta

19

estoy un poco tarde a esta pregunta, pero creo que tengo alguna información pertinente.

Las propuestas de shared_mutex al comité C++, en las que se basan las bibliotecas de impulso, a propósito no especificaron una API para dar prioridad a los lectores ni a los escritores. Esto es porque Alexander Terekhov propuso un algoritmo hace una década que es completamente justo. Permite que el sistema operativo decida si el siguiente hilo para obtener el mutex es un lector o escritor, y el sistema operativo es completamente ignorante en cuanto a si el siguiente hilo es un lector o escritor.

Debido a este algoritmo, la necesidad de especificar si un lector o escritor es preferido desaparece. Que yo sepa, las libs de impulso están ahora (impulso 1.52) implementadas con este algoritmo justo.

El algoritmo de Terekhov consiste en tener el mutex de lectura/escritura compuesto por dos puertas: gate1 y gate2. Solo un hilo a la vez puede atravesar cada puerta. Las puertas se pueden implementar con un mutex y dos variables de condición.

Ambos lectores y escritores intentan pasar por gate1. Para pasar por gate1, debe ser cierto que un hilo de escritor no se encuentra actualmente dentro de gate1. Si lo hay, el hilo intenta pasar por los bloques de gate1.

Una vez que un hilo lector pasa por la puerta 1, ha leído la propiedad del mutex.

Cuando un hilo de escritor pasa a través de gate1, también debe pasar por gate2 antes de obtener la propiedad de escritura del mutex. No puede pasar a través de gate2 hasta que la cantidad de lectores dentro de gate1 caiga a cero.

Este es un algoritmo justo porque cuando solo hay 0 o más lectores dentro de gate1, le corresponde al sistema operativo si el siguiente hilo para entrar en gate1 es un lector o escritor. Un escritor se convierte en "prioritario" solo después de que ha pasado a través de gate1, y es el siguiente en la línea para obtener la propiedad del mutex.

He utilizado su ejemplo compilado contra una implementación de ejemplo de lo que finalmente se convirtió en shared_timed_mutex en C++ 14 (con modificaciones menores en su ejemplo). El siguiente código lo llama shared_mutex, que es el nombre que tenía cuando se lo propuso.

me dieron los siguientes resultados (todos con el mismo ejecutable):

veces:

Worked finished her work 
Worked finished her work 
Grabbed exclusive lock, killing system 
KILLING ALL WORK 
Workers are on strike. This worker refuses to work 
Workers are on strike. This worker refuses to work 

Y a veces:

Worked finished her work 
Grabbed exclusive lock, killing system 
KILLING ALL WORK 
Workers are on strike. This worker refuses to work 
Workers are on strike. This worker refuses to work 
Workers are on strike. This worker refuses to work 

Y a veces:

Worked finished her work 
Worked finished her work 
Worked finished her work 
Worked finished her work 
Grabbed exclusive lock, killing system 
KILLING ALL WORK 

yo Creo que debería ser teóricamente posible obtener otros resultados, aunque no lo confirmé experimentalmente.

En aras de la divulgación completa, aquí es el código exacto ejecuté:

#include "../mutexes/shared_mutex" 
#include <thread> 
#include <chrono> 
#include <iostream> 

using namespace std; 
using namespace ting; 

mutex outLock; 
shared_mutex workerAccess; 
bool shouldIWork = true; 

class WorkerKiller 
{ 
public: 

    void operator()() 
    { 
     unique_lock<shared_mutex> lock(workerAccess); 

     cout << "Grabbed exclusive lock, killing system" << endl; 
     this_thread::sleep_for(chrono::seconds(2)); 
     shouldIWork = false; 
     cout << "KILLING ALL WORK" << endl; 
    } 

private: 
}; 

class Worker 
{ 
public: 

    Worker() 
    { 
    } 

    void operator()() 
    { 
     shared_lock<shared_mutex> lock(workerAccess); 

     if (!shouldIWork) { 
      lock_guard<mutex> _(outLock); 
      cout << "Workers are on strike. This worker refuses to work" << endl; 
     } else { 
      this_thread::sleep_for(chrono::seconds(1)); 

      lock_guard<mutex> _(outLock); 
      cout << "Worked finished her work" << endl; 
     } 
    } 
}; 

int main() 
{ 
    Worker w1; 
    Worker w2; 
    Worker w3; 
    Worker w4; 
    WorkerKiller wk; 

    thread workerThread1(w1); 
    thread workerThread2(w2); 

    thread workerKillerThread(wk); 

    thread workerThread3(w3); 
    thread workerThread4(w4); 

    workerThread1.join(); 
    workerThread2.join(); 
    workerKillerThread.join(); 
    workerThread3.join(); 
    workerThread4.join(); 

    return 0; 
} 
+0

Sí, acabo de volver a esta pregunta para publicar esta misma respuesta. Usted es de hecho correcto. Boost utiliza ahora una implementación justa y no se puede controlar cuál se selecciona. Muchas gracias por su explicación detallada. – anoneironaut

+0

El papel de bloomington parece haber desaparecido del nuevo sitio en http://howardhinnant.github.io/. No sé cómo arreglarlo :) – sehe

1

Una búsqueda en Google de "inanición bloqueo impulsar compartida" da como resultado este enlace:

Parece que "actualizar" podría ser la clave. Consulte también:

+0

Hola Pablo. Eché un vistazo a esos artículos. Fueron excelentes lecturas, pero todavía estoy un poco perdido en cuanto a si puedo o no configurar estos mutexes como Escribir preferido. El último artículo parece delinear un modelo mutex que el impulso es similar también, pero tiene diferencias clave. En ese modelo, definitivamente es preferible escribir. Pero en el impulso, parece ser específico del sistema operativo? Encontré esta información en la documentación 1.35 también: http://www.boost.org/doc/libs/1_34_0/doc/html/thread/concepts.html#thread.concepts.read-write-scheduling-policies.inter -clase. Además intenté elevar la prioridad de mi escritor. – anoneironaut

+0

@HowardHinnant Intenté [arreglar este enlace] (http://stackoverflow.com/posts/12082441/revisions). Tal vez podrías revisarlo – sehe

Cuestiones relacionadas