2012-10-02 13 views
8

Tengo dos hilos, uno que trabaja en un bucle estrecho, y el otro, que en ocasiones resulta necesario realizar una sincronización con la primera:pthreads: el hambre hilo causada por una rápida re-bloqueo

// thread 1 
while(1) 
{ 
    lock(work); 
    // perform work 
    unlock(work); 
} 

// thread 2 
while(1) 
{ 
    // unrelated work that takes a while 
    lock(work); 
    // synchronizing step 
    unlock(work); 
} 

Mi intención es que el hilo 2 puede, al tomar el bloqueo, pausar efectivamente el hilo 1 y realizar la sincronización necesaria. El subproceso 1 también puede ofrecer pausa, al desbloquear, y si el subproceso 2 no está esperando en el bloqueo, vuelva a bloquearlo y vuelva a funcionar.

El problema que he encontrado es que los mutexes no son justos, por lo que el hilo 1 rápidamente vuelve a bloquear el mutex y ahoga el hilo 2. He intentado usar pthread_yield, y hasta ahora parece funcionar bien, pero no estoy Seguro que funcionará para todos los sistemas/número de núcleos. ¿Hay alguna manera de garantizar que el hilo 1 siempre cederá al hilo 2, incluso en sistemas multi-core?

¿Cuál es la forma más efectiva de manejar este proceso de sincronización?

+0

Lo que se ha descrito en su pregunta no tiene relación con la equidad, si usted quiere Tema 2 sea de mayor prioridad luego pase 1, una de las maneras para lograrlo, mediante el uso de variables de condición. – Michael

+0

Esta pregunta es muy similar a [* sección crítica Fair (Linux) *] (http://stackoverflow.com/q/6449732/134633). – caf

Respuesta

3

Se puede construir una FIFO "bloqueo de entradas" en la parte superior de pthreads mutex, a lo largo de estas líneas:

#include <pthread.h> 

typedef struct ticket_lock { 
    pthread_cond_t cond; 
    pthread_mutex_t mutex; 
    unsigned long queue_head, queue_tail; 
} ticket_lock_t; 

#define TICKET_LOCK_INITIALIZER { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER } 

void ticket_lock(ticket_lock_t *ticket) 
{ 
    unsigned long queue_me; 

    pthread_mutex_lock(&ticket->mutex); 
    queue_me = ticket->queue_tail++; 
    while (queue_me != ticket->queue_head) 
    { 
     pthread_cond_wait(&ticket->cond, &ticket->mutex); 
    } 
    pthread_mutex_unlock(&ticket->mutex); 
} 

void ticket_unlock(ticket_lock_t *ticket) 
{ 
    pthread_mutex_lock(&ticket->mutex); 
    ticket->queue_head++; 
    pthread_cond_broadcast(&ticket->cond); 
    pthread_mutex_unlock(&ticket->mutex); 
} 

Bajo este tipo de esquema, sin pthreads de bajo nivel de exclusión mutua se mantiene mientras que un hilo está dentro del sección crítica protegida de ticketlock, permitiendo que otros hilos se unan a la cola.

5

En su caso, es mejor utilizar condition variable para notificar el segundo hilo cuando se requiere que se despierte y realice todas las operaciones requeridas.

+1

¡También mencione los semáforos como una posible solución a problemas como estos, a menudo se pasan por alto! –

+0

¿Algún ejemplo o fragmento sobre cómo los semáforos se pueden usar para este problema? – Atanu

+0

[Esta respuesta mío] (http://stackoverflow.com/a/6453925/134633) podría ser útil, donde muestro cómo utilizar las variables de condición pthreads para implementar un primero en entrar, primero en salir bloqueo billete. – caf

2

pthread ofrece una noción de prioridad de subprocesos en su API. Cuando dos hilos compiten en un mutex, la política de programación determina cuál la obtendrá. La función pthread_attr_setschedpolicy le permite configurar eso, y pthread_attr_getschedpolicy permite recuperar la información.

Ahora las malas noticias:

  • Cuando sólo dos hilos son de bloqueo/desbloqueo de un mutex, no veo ningún tipo de competencia, el primero que se ejecuta la instrucción atómica se lo lleva, los otros bloques. No estoy seguro de si este atributo se aplica aquí.
  • La función puede tomar diferentes parámetros (SCHED_FIFO, SCHED_RR, SCHED_OTHER y SCHED_SPORADIC), pero in this question, se ha contestado que sólo SCHED_OTHER fue soportado en Linux)

Así que le daría un tiro si yo fuera usted, pero no espere demasiado. pthread_yield me parece más prometedor. Más información disponible here.

0

El bloqueo de tickets de arriba se ve como el mejor. Sin embargo, para asegurar que pthread_yield funcione, puede tener un bool en espera, que se configura y restablece mediante thread2. thread1 cede mientras se establezca bool waiting.

0

Aquí hay una solución simple que funcionará para su caso (dos hilos). Si usa std::mutex, esta clase es un reemplazo inmediato. Cambie su mutex a este tipo y se le garantiza que si un hilo mantiene el bloqueo y el otro lo espera, una vez que el primer hilo se desbloquea, el segundo hilo captura el bloqueo antes de que el primer hilo pueda volver a bloquearlo.

Si hay más de dos hilos pasan a utilizar el mutex al mismo tiempo que seguirá funcionando pero no hay garantías en la justicia.

Si está utilizando llanura pthread_mutex_t puede cambiar fácilmente su código de bloqueo de acuerdo con este ejemplo (desbloqueo se mantiene sin cambios).

#include <mutex> 

// Behaves the same as std::mutex but guarantees fairness as long as 
// up to two threads are using (holding/waiting on) it. 
// When one thread unlocks the mutex while another is waiting on it, 
// the other is guaranteed to run before the first thread can lock it again. 

class FairDualMutex : public std::mutex { 
public: 
    void lock() { 
     _fairness_mutex.lock(); 
     std::mutex::lock(); 
     _fairness_mutex.unlock(); 
    } 
private: 
    std::mutex _fairness_mutex; 
}; 
Cuestiones relacionadas