Saltar al contenido

¿Por qué las funciones de variable de condición de pthreads requieren un mutex?

Solución:

Es simplemente la forma en que se implementan (o se implementaron originalmente) las variables de condición.

El mutex se usa para proteger la variable de condición en sí. Es por eso que lo necesita bloqueado antes de esperar.

La espera desbloqueará “atómicamente” el mutex, permitiendo que otros accedan a la variable de condición (para señalización). Luego, cuando la variable de condición sea señalizada o transmitida a, uno o más de los subprocesos en la lista de espera se despertarán y el mutex se bloqueará mágicamente nuevamente para ese subproceso.

Normalmente verá la siguiente operación con variables de condición, que ilustra cómo funcionan. El siguiente ejemplo es un subproceso de trabajo al que se le da trabajo a través de una señal a una variable de condición.

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            do the work.
    unlock mutex.
    clean up.
    exit thread.

El trabajo se realiza dentro de este ciclo siempre que haya algo disponible cuando vuelva la espera. Cuando el subproceso ha sido marcado para dejar de funcionar (generalmente por otro subproceso que establece la condición de salida y luego patea la variable de condición para activar este subproceso), el bucle saldrá, el mutex se desbloqueará y este subproceso saldrá.

El código anterior es un modelo de un solo consumidor, ya que el mutex permanece bloqueado mientras se realiza el trabajo. Para una variación de varios consumidores, puede utilizar, como ejemplo:

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            copy work to thread local storage.
            unlock mutex.
            do the work.
            lock mutex.
    unlock mutex.
    clean up.
    exit thread.

lo que permite que otros consumidores reciban trabajo mientras éste está trabajando.

La variable de condición lo libera de la carga de sondear alguna condición en lugar de permitir que otro hilo le notifique cuando algo debe suceder. Otro hilo puede indicar que ese hilo está disponible de la siguiente manera:

lock mutex.
flag work as available.
signal condition variable.
unlock mutex.

La gran mayoría de lo que a menudo se denominan erróneamente activaciones espúreas se debió generalmente a que se habían señalado varios subprocesos dentro de su pthread_cond_wait call (broadcast), uno volvería con el mutex, haría el trabajo y luego volvería a esperar.

Entonces, el segundo hilo señalado podría salir cuando no había trabajo por hacer. Por lo tanto, tenía que tener una variable adicional que indicara que se debería hacer el trabajo (esto estaba inherentemente protegido por mutex con el par condvar / mutex aquí; sin embargo, otros subprocesos necesitaban bloquear el mutex antes de cambiarlo).

Eso era técnicamente es posible que un hilo regrese de una condición de espera sin ser pateado por otro proceso (esto es un despertar falso genuino) pero, en todos mis años trabajando en pthreads, tanto en el desarrollo / servicio del código como como usuario de ellos , Nunca recibí uno de estos. Tal vez fue solo porque HP tuvo una implementación decente 🙂

En cualquier caso, el mismo código que manejó el caso erróneo también manejó reactivaciones espúreas genuinas, ya que la bandera de trabajo disponible no se establecería para esas.

Una variable de condición es bastante limitada si solo puede señalar una condición; por lo general, necesita manejar algunos datos relacionados con la condición que se señaló. La señalización / activación debe realizarse de forma atómica para lograr eso sin introducir condiciones de carrera, o ser demasiado complejo

pthreads también puede darle, por razones bastante técnicas, una falsa activación. Eso significa que debe verificar un predicado, por lo que puede estar seguro de que la condición realmente se señaló, y distinguir eso de una activación falsa. La verificación de dicha condición con respecto a la espera debe ser protegida, por lo que una variable de condición necesita una forma de esperar / despertar atómicamente mientras se bloquea / desbloquea un mutex que protege esa condición.

Considere un ejemplo simple en el que se le notifica que se producen algunos datos. Tal vez otro hilo hizo algunos datos que desea y estableció un puntero a esos datos.

Imagine un hilo de productor dando algunos datos a otro hilo de consumidor a través de un puntero ‘some_data’.

while(1) {
    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    char *data = some_data;
    some_data = NULL;
    handle(data);
}

naturalmente obtendrías muchas condiciones de carrera, ¿y si el otro hilo lo hiciera? some_data = new_data justo después de que te despertaran, pero antes de que lo hicieras data = some_data

Realmente no puede crear su propio mutex para proteger este caso tampoco .eg

while(1) {

    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    pthread_mutex_lock(&mutex);
    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

No funcionará, todavía existe la posibilidad de una condición de carrera entre el despertar y tomar el mutex. Colocar el mutex antes del pthread_cond_wait no le ayuda, ya que ahora mantendrá el mutex mientras espera, es decir, el productor nunca podrá tomar el mutex. (tenga en cuenta que, en este caso, podría crear una segunda variable de condición para indicarle al productor que ha terminado con some_data – aunque esto se volverá complejo, especialmente si desea muchos productores / consumidores).

Por lo tanto, necesita una forma de liberar / agarrar atómicamente el mutex cuando espera / despierta de la condición. Eso es lo que hacen las variables de condición pthread, y esto es lo que harías:

while(1) {
    pthread_mutex_lock(&mutex);
    while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also 
                               // make it robust if there were several consumers
       pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
    }

    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

(el productor, naturalmente, necesitaría tomar las mismas precauciones, siempre protegiendo ‘some_data’ con el mismo mutex, y asegurándose de que no sobrescriba some_data si some_data es actualmente! = NULL)

Las variables de condición POSIX no tienen estado. De modo que es su responsabilidad mantener el estado. Dado que tanto los subprocesos que esperan como los subprocesos que le dicen a otros subprocesos que dejen de esperar accederán al estado, debe estar protegido por un mutex. Si cree que puede usar variables de condición sin un mutex, entonces no ha comprendido que las variables de condición no tienen estado.

Las variables de condición se construyen alrededor de una condición. Los subprocesos que esperan una variable de condición están esperando alguna condición. Los subprocesos que señalan las variables de condición cambian esa condición. Por ejemplo, un hilo puede estar esperando a que lleguen algunos datos. Algún otro hilo puede notar que los datos han llegado. “Los datos han llegado” es la condición.

Aquí está el uso clásico de una variable de condición, simplificado:

while(1)
{
    pthread_mutex_lock(&work_mutex);

    while (work_queue_empty())       // wait for work
       pthread_cond_wait(&work_cv, &work_mutex);

    work = get_work_from_queue();    // get work

    pthread_mutex_unlock(&work_mutex);

    do_work(work);                   // do that work
}

Vea cómo el hilo está esperando el trabajo. El trabajo está protegido por un mutex. La espera libera el mutex para que otro hilo pueda darle algo de trabajo a este hilo. Así es como se señalizaría:

void AssignWork(WorkItem work)
{
    pthread_mutex_lock(&work_mutex);

    add_work_to_queue(work);           // put work item on queue

    pthread_cond_signal(&work_cv);     // wake worker thread

    pthread_mutex_unlock(&work_mutex);
}

Note que usted necesitar el mutex para proteger la cola de trabajo. Observe que la variable de condición en sí misma no tiene idea de si hay trabajo o no. Es decir, una variable de condición debe estar asociado con una condición, esa condición debe ser mantenida por su código, y dado que se comparte entre subprocesos, debe estar protegida por un mutex.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *