¿Esperar en múltiples variables de condición en Linux sin dormir innecesariamente?


Estoy escribiendo una aplicación sensible a la latencia que en efecto quiere esperar en múltiples variables de condición a la vez. He leído antes de varias maneras de obtener esta funcionalidad en Linux (al parecer esto es builtin en Windows), pero ninguno de ellos parece adecuado para mi aplicación. Los métodos que conozco son:

  1. Tenga un hilo de espera en cada una de las variables de condición que desea esperar, que cuando se despierta señalará una sola variable de condición que espera en su lugar.

  2. Ciclo a través de múltiples variables de condición con una espera temporizada.

  3. Escribir bytes ficticios en archivos o tuberías en su lugar, y sondear en ellos.

#1 & #2 son inadecuados porque causan un sueño innecesario. Con #1, tienes que esperar a que el hilo falso se despierte, luego señalar el hilo real, luego para que el hilo real se despierte, en lugar de que el hilo real se despierte para empezar the el quantum del programador adicional gastado en esto en realidad importa para mi aplicación, y preferiría no tener que usar un RTOS completo. #2 es aún peor, potencialmente pasas N * tiempo de espera dormido , o tu tiempo de espera será 0 en cuyo caso nunca duermes (quemar CPU sin fin y matar de hambre a otros hilos también es malo).

Para #3, las tuberías son problemáticas porque si el hilo que se 'señala' está ocupado o incluso se bloquea ( de hecho, estoy tratando con procesos separados en lugar de hilos the los mutexes y las condiciones se almacenarían en compartido memoria), entonces el hilo de escritura se atascará porque el búfer de la tubería estará lleno, al igual que cualquier otro cliente. Los archivos son problemáticos porque lo estarás creciendo sin fin cuanto más tiempo se ejecute la aplicación.

¿Hay una mejor manera de hacer esto? Curioso por respuestas apropiadas para Solaris también.

Author: Roman Nikitchenko, 2010-05-17

4 answers

Si está hablando de hilos POSIX, le recomiendo usar una sola variable de condición y el número de indicadores de evento o algo similar. La idea es usar peer condvar mutex para proteger las notificaciones de eventos. De todos modos necesitas comprobar si hay algún evento después de la salida de cond_wait (). Aquí está mi código lo suficientemente viejo para ilustrar esto de mi entrenamiento (sí, comprobé que funciona, pero tenga en cuenta que fue preparado hace algún tiempo y con prisa para los recién llegados).

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static pthread_cond_t var;
static pthread_mutex_t mtx;

unsigned event_flags = 0;
#define FLAG_EVENT_1    1
#define FLAG_EVENT_2    2

void signal_1()
{
    pthread_mutex_lock(&mtx);
    event_flags |= FLAG_EVENT_1;
    pthread_cond_signal(&var);
    pthread_mutex_unlock(&mtx);
}

void signal_2()
{
    pthread_mutex_lock(&mtx);
    event_flags |= FLAG_EVENT_2;
    pthread_cond_signal(&var);
    pthread_mutex_unlock(&mtx);
}

void* handler(void*)
{
    // Mutex is unlocked only when we wait or process received events.
    pthread_mutex_lock(&mtx);

    // Here should be race-condition prevention in real code.

    while(1)
    {
        if (event_flags)
        {
            unsigned copy = event_flags;

            // We unlock mutex while we are processing received events.
            pthread_mutex_unlock(&mtx);

            if (event_flags & FLAG_EVENT_1)
            {
                printf("EVENT 1\n");
                event_flags ^= FLAG_EVENT_1;
            }

            if (event_flags & FLAG_EVENT_2)
            {
                printf("EVENT 2\n");
                event_flags ^= FLAG_EVENT_2;

                // And let EVENT 2 is signal to close.
                // In this case for consistency we break with locked mutex.
                pthread_mutex_lock(&mtx);
                break;
            }

            // Note we should have mutex locked at the iteration end.
            pthread_mutex_lock(&mtx);
        }
        else
        {
            // Mutex is locked. It is unlocked while we are waiting.
            pthread_cond_wait(&var, &mtx);
            // Mutex is locked.
        }
    }

    // ... as we are dying.
    pthread_mutex_unlock(&mtx);
}

int main()
{
    pthread_mutex_init(&mtx, NULL);
    pthread_cond_init(&var, NULL);

    pthread_t id;
    pthread_create(&id, NULL, handler, NULL);
    sleep(1);

    signal_1();
    sleep(1);
    signal_1();
    sleep(1);
    signal_2();
    sleep(1);

    pthread_join(id, NULL);
    return 0;
}
 11
Author: Roman Nikitchenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 13:55:05

Su opción #3 (escribir bytes ficticios en archivos o tuberías en su lugar, y sondear en ellos) tiene una mejor alternativa en Linux: eventfd.

En lugar de un búfer de tamaño limitado (como en una tubería) o un búfer de crecimiento infinito (como en un archivo), con eventfd tiene un contador de 64 bits sin signo en el núcleo. Un 8-byte write agrega un número al contador; un 8-byte read o bien cera el contador y devuelve su valor anterior (sin EFD_SEMAPHORE), o decrementa el contador por 1 y devuelve 1 (con EFD_SEMAPHORE). El descriptor de archivo se considera legible para las funciones de sondeo(select, poll, epoll) cuando el contador es distinto de cero.

Incluso si el contador está cerca del límite de 64 bits, el write simplemente fallará con EAGAIN si hizo que el descriptor de archivo no bloquee. Lo mismo sucede con read cuando el contador es cero.

 15
Author: CesarB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-04 00:55:42

Para esperar en múltiples variables de condición, hay una implementación para Solaris que puede portar a Linux si está interesado: waitFor API

 2
Author: Enforcer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-20 06:59:57

Si desea la máxima flexibilidad bajo el modelo de sincronización de variables de condición POSIX, debe evitar escribir módulos que comuniquen eventos a sus usuarios solo mediante la exposición de una variable de condición. (Entonces esencialmente has reinventado un semáforo.)

Los módulos activos deben diseñarse de tal manera que sus interfaces proporcionen notificaciones de devolución de llamada de eventos, a través de funciones registradas: y, si es necesario, de tal manera que se puedan registrar múltiples devoluciones de llamada.

Un cliente de múltiples módulos registra una devolución de llamada con cada uno de ellos. Todos estos pueden ser enrutados a un lugar común donde bloquean el mismo mutex, cambian algún estado, desbloquean y golpean la misma variable de condición.

Este diseño también ofrece la posibilidad de que, si la cantidad de trabajo realizado en respuesta a un evento es razonablemente pequeña, tal vez solo se pueda hacer en el contexto de la devolución de llamada.

Las devoluciones de llamada también tienen algunas ventajas en la depuración. Puede poner un punto de interrupción en un evento que llega en forma de devolución de llamada, y ve la pila de llamadas de cómo se generó. Si pones un punto de interrupción en un evento que llega como un despertar de semáforo, o a través de algún mecanismo de paso de mensajes, el seguimiento de llamada no revela el origen del evento.


Dicho esto, puede hacer sus propias primitivas de sincronización con mutexes y variables de condición que admiten la espera en varios objetos. Estas primitivas de sincronización pueden basarse internamente en devoluciones de llamada, de una manera que es invisible para el resto de la aplicación.

La esencia de esto es que para cada objeto que un hilo quiere esperar, la operación wait pone en cola una interfaz de devolución de llamada con ese objeto. Cuando un objeto es señalado, invoca todas sus devoluciones de llamada registradas. Los hilos despertados desqueuean todas las interfaces de devolución de llamada, y echan un vistazo a algunos indicadores de estado en cada uno para ver qué objetos señalaron.

 2
Author: Kaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-23 22:57:26