Dlaczego funkcje zmiennych warunkowych pthreads wymagają mutex?

Czytam pthread.h; funkcje związane ze zmienną warunkową (jak pthread_cond_wait(3)) wymagają mutex jako argumentu. Dlaczego? Z tego co wiem, to będę tworzyć mutex Tylko , Aby użyć jako tego argumentu? Co ten mutex ma zrobić?

Author: ELLIOTTCABLE, 2010-05-04

9 answers

To po prostu sposób, w jaki zmienne warunkowe są (lub były pierwotnie) implementowane.

Mutex jest używany do ochrony samej zmiennej warunkowej . Dlatego musisz je zamknąć, zanim poczekasz.

Wait" atomicznie " odblokuje mutex, umożliwiając innym dostęp do zmiennej warunkowej (dla sygnalizacji). Następnie, gdy zmienna warunkowa jest sygnalizowana lub nadawana, jeden lub więcej wątków na liście oczekujących zostanie obudzonych, a mutex zostanie magicznie zablokowany ponownie dla tego wątku.

Zazwyczaj widzisz następującą operację ze zmiennymi warunkowymi, ilustrującą ich działanie. Poniższy przykład to wątek roboczy, który jest podawany do pracy poprzez sygnał do zmiennej warunkowej.

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.

Praca jest wykonywana w tej pętli, pod warunkiem, że niektóre są dostępne po powrocie oczekiwania. Gdy wątek został oznaczony, aby przestał wykonywać pracę (zwykle przez inny wątek ustawiający warunek wyjścia, a następnie kopiący warunek zmienna, aby obudzić ten wątek), pętla zakończy się, mutex zostanie odblokowany i ten wątek zakończy się.

Powyższy kod jest modelem dla jednego konsumenta, ponieważ mutex pozostaje zablokowany podczas wykonywania pracy. Dla odmiany wielomodułowej można użyć, jako przykład :

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.

Który pozwala innym konsumentom na otrzymywanie pracy, podczas gdy ten wykonuje pracę.

Zmienna warunkowa zwalnia cię z ciężaru ankietowania jakiegoś warunku zamiast zezwalania na inny wątek, aby powiadomić cię, gdy coś musi się wydarzyć. Inny wątek może powiedzieć, że wątek, który działa jest dostępny w następujący sposób:

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

Zdecydowana większość tego, co często błędnie nazywane jest fałszywymi wakeupami, była zazwyczaj zawsze dlatego, że wiele wątków zostało zasygnalizowanych w ich wywołaniu (transmisji), wracało się z mutexem, wykonywało pracę, a następnie ponownie czekało.

Wtedy drugi sygnalizowany wątek mógł wyjść, gdy nie było pracy do wykonania. Więc trzeba było mieć dodatkowe zmienna wskazująca, że praca powinna być wykonana (była to z natury mutex-chroniona parą condvar/mutex tutaj - inne wątki musiały jednak zablokować mutex przed jego zmianą).

To było technicznie możliwe, aby wątek wrócił z warunkowego oczekiwania bez kopnięcia przez inny proces (jest to prawdziwy fałszywy przebudzenie), ale w ciągu wszystkich moich wielu lat pracy nad pthreads, zarówno w rozwoju/obsłudze kodu, jak i jako użytkownik ich, nigdy nie otrzymałem żadnego z tych pytań. to. Może to dlatego, że HP miało przyzwoitą implementację: -)

W każdym razie ten sam kod, który obsługiwał błędną sprawę, również obsługiwał prawdziwe fałszywe przebudzenia, ponieważ flaga dostępna dla pracy nie byłaby ustawiona dla nich.

 170
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-26 22:00:58

Zmienna warunkowa jest dość ograniczona, jeśli możesz tylko zasygnalizować warunek, zwykle musisz obsłużyć niektóre dane, które są związane z warunkiem, który został zasygnalizowany. Aby osiągnąć to bez wprowadzania warunków wyścigowych, należy wykonać sygnalizację/przebudzenie, lub być nadmiernie złożonym

Pthreads może również dać ci, z powodów raczej technicznych, fałszywe przebudzenie . Oznacza to, że musisz sprawdzić predykat, więc możesz mieć pewność, że warunek faktycznie był sygnalizowane - i odróżnić to od fałszywego przebudzenia. Sprawdzanie takiego warunku w odniesieniu do czekania na to musi być strzeżone - więc zmienna warunkowa potrzebuje sposobu na atomiczne czekanie/budzenie się podczas blokowania / odblokowywania mutex chroniącego ten warunek.

Rozważ prosty przykład, w którym otrzymujesz powiadomienie o wytwarzaniu niektórych danych. Może inny wątek zrobił jakieś dane, które chcesz i ustawić wskaźnik do tych danych.

Wyobraź sobie wątek producenta dający pewne dane innemu konsumentowi thread through a 'some_data' pointer.

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

Naturalnie dostałbyś dużo kondycji rasowej, co jeśli drugi wątek zrobił some_data = new_data zaraz po tym, jak się obudziłeś, ale zanim to zrobiłeś data = some_data {7]} Nie możesz też stworzyć własnego mutexu, by chronić tę sprawę .e. G

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);
}

Nie zadziała, nadal jest szansa na stan rasy pomiędzy obudzeniem się a złapaniem mutex. Umieszczenie mutexu przed pthread_cond_wait Ci nie pomoże, tak jak teraz przytrzymaj mutex podczas czekanie - czyli producent nigdy nie będzie w stanie złapać mutex ' a. (uwaga, w tym przypadku możesz utworzyć drugą zmienną warunkową, aby zasygnalizować producentowi, że skończyłeś z some_data - choć stanie się to skomplikowane, zwłaszcza jeśli chcesz wielu producentów/konsumentów.)

Dlatego potrzebujesz sposobu na atomiczne uwolnienie / złapanie mutex ' a podczas oczekiwania/budzenia się z warunku. To właśnie robi pthread condition variables, a oto co byś zrobił:

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);
}

(producent by oczywiście trzeba podjąć te same środki ostrożności, zawsze pilnując 'some_data' z tym samym mutex, i upewniając się, że nie nadpisuje some_data, jeśli some_data jest obecnie != NULL)

 54
Author: nos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-08 16:23:42

Zmienne warunkowe POSIX są bezpaństwowe. Więc twoim obowiązkiem jest utrzymanie Państwa. Ponieważ stan będzie dostępny zarówno dla wątków, które czekają, jak i dla wątków, które każą innym wątkom przestać czekać, musi być chroniony przez mutex. Jeśli uważasz, że możesz używać zmiennych warunkowych bez mutex, to nie zrozumiałeś, że zmienne warunkowe są bezstanowe.

Zmienne warunkowe są zbudowane wokół warunku. Wątki oczekujące na zmienną warunkową czekają na jakieś warunek. Wątki, które sygnalizują zmienne warunkowe, zmieniają ten warunek. Na przykład wątek może czekać na nadejście niektórych danych. Inny wątek może zauważyć, że dane dotarły. "Dane dotarły" jest warunkiem.

Oto Klasyczne użycie zmiennej warunkowej, uproszczone:

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
}

Zobacz, jak wątek czeka na pracę. Praca jest chroniona przez mutex. Wait uwalnia mutex tak, że inny wątek może dać temu wątkowi trochę pracy. Oto jak to będzie sygnalizowane:

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);
}

Zauważ, że potrzebujesz mutexu, aby chronić kolejkę roboczą. Zauważ, że sama zmienna warunkowa nie ma pojęcia, czy działa, czy nie. Oznacza to, że zmienna warunkowa musi być powiązana z warunkiem, warunek ten musi być utrzymywany przez Twój kod, a ponieważ jest współdzielona między wątkami, musi być chroniona przez mutex.

 28
Author: David Schwartz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-24 11:38:07

Nie wszystkie funkcje zmiennych warunkowych wymagają mutex - a tylko operacje oczekujące. Operacje sygnałowe i transmisyjne nie wymagają mutex. Zmienna warunkowa również nie jest trwale powiązana z konkretnym mutex; zewnętrzny mutex nie chroni zmiennej warunkowej. Jeśli zmienna warunkowa ma wewnętrzny stan, taki jak kolejka oczekujących wątków, musi być zabezpieczona wewnętrzną blokadą wewnątrz zmiennej warunkowej.

Operacje wait łączą w sobie zmienna warunkowa i mutex, ponieważ:

  • wątek zablokował mutex, ocenił jakieś wyrażenie nad współdzielonymi zmiennymi i uznał je za false, tak że musi poczekać.
  • wątek musi atomicznie przejść od posiadania mutex, do oczekiwania na warunek.

Z tego powodu, operacja wait przyjmuje jako argumenty zarówno mutex, jak i condition: tak, że może zarządzać transferem atomowym wątku z posiadania mutex do oczekiwania, tak, że wątek nie pada ofiarą lost wake up race condition .

Warunek lost wakeup race pojawi się, jeśli wątek zrezygnuje z mutex, a następnie czeka na obiekt synchronizacji bez stanu, ale w sposób, który nie jest atomowy: istnieje okno czasu, gdy wątek nie ma już blokady i nie rozpoczął jeszcze czekania na obiekt. Podczas tego okna może wejść inny wątek, sprawić, że oczekiwany warunek będzie prawdziwy, zasygnalizować bezstanową synchronizację, a następnie zniknąć. Obiekt bezstanowy nie pamięta, że został zasygnalizowany (jest bezstanowy). Tak więc oryginalny wątek przechodzi w stan uśpienia na obiekcie synchronizacji bezstanowej i nie budzi się, nawet jeśli warunek, którego potrzebuje, już się spełnił: utracone przebudzenie.

Funkcje zmiennej warunkowej wait unikają utraconego przebudzenia, upewniając się, że wątek wywołujący jest zarejestrowany, aby niezawodnie złapać przebudzenie, zanim zrezygnuje z mutex. Byłoby to niemożliwe, gdyby zmienna warunkowa wait funkcja nie przyjęła mutex jako argumentu.

 12
Author: Kaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-24 23:00:05

Mutex powinien być zablokowany podczas wywoływania pthread_cond_wait; kiedy go wywołujesz, atomicznie odblokowuje mutex, a następnie blokuje pod warunkiem. Gdy stan zostanie zasygnalizowany, atomicznie blokuje go ponownie i powraca.

Pozwala to na implementację przewidywalnego harmonogramu w razie potrzeby, w tym wątku, który wykonuje sygnalizację, może poczekać, aż mutex zostanie zwolniony, aby wykonać jego przetwarzanie, a następnie zasygnalizować warunek.

 3
Author: Amber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-04 08:12:40

Zmienne warunkowe są powiązane z mutexem, ponieważ jest to jedyny sposób, aby uniknąć rasy, której ma unikać.

// incorrect usage:
// thread 1:
while (notDone) {
    pthread_mutex_lock(&mutex);
    bool ready = protectedReadyToRunVariable
    pthread_mutex_unlock(&mutex);
    if (ready) {
        doWork();
    } else {
        pthread_cond_wait(&cond1); // invalid syntax: this SHOULD have a mutex
    }
}

// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
   protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);

Now, lets look at a particularly nasty interleaving of these operations

pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable;
pthread_mutex_unlock(&mutex);
                                 pthread_mutex_lock(&mutex);
                                 protectedReadyToRuNVariable = true;
                                 pthread_mutex_unlock(&mutex);
                                 pthread_cond_signal(&cond1);
if (ready) {
pthread_cond_wait(&cond1); // uh o!

W tym momencie nie ma wątku, który będzie sygnalizować zmienną warunkową, więc thread1 będzie czekał w nieskończoność, nawet jeśli protectedReadyToRunVariable mówi, że jest gotowy do pracy!

Jedynym sposobem na obejście tego jest to, aby zmienne warunkowe atomicznie zwolniły mutex, jednocześnie zaczynając czekać na warunek zmienna. Dlatego funkcja cond_wait wymaga mutex

// correct usage:
// thread 1:
while (notDone) {
    pthread_mutex_lock(&mutex);
    bool ready = protectedReadyToRunVariable
    if (ready) {
        pthread_mutex_unlock(&mutex);
        doWork();
    } else {
        pthread_cond_wait(&mutex, &cond1);
    }
}

// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
   protectedReadyToRuNVariable = true;
   pthread_cond_signal(&mutex, &cond1);
pthread_mutex_unlock(&mutex);
 3
Author: Cort Ammon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-04 03:32:16

Nie uważam, aby inne odpowiedzi były tak zwięzłe i czytelne jak ta strona . Normalnie kod oczekujący wygląda mniej więcej tak:

mutex.lock()
while(!check())
    condition.wait()
mutex.unlock()

Istnieją trzy powody, aby zawinąć wait() w mutex:

  1. BEZ mutexu inny wątek mógłby signal() przed wait() i przegapilibyśmy to przebudzenie.
  2. normalnie check() jest zależny od modyfikacji z innego wątku, więc i tak potrzebujesz wzajemnego wykluczenia.
  3. aby zapewnić najwyższy priorytet wątek przebiega pierwszy (kolejka mutex pozwala schedulerowi zdecydować, kto będzie następny).

Trzeci punkt nie zawsze jest niepokojący - kontekst historyczny jest powiązany z artykułem do tej rozmowy .

Fałszywe pobudki są często wspominane w odniesieniu do tego mechanizmu (tzn. wątek oczekujący jest budzony bez wywoływania signal()). Jednak takie zdarzenia są obsługiwane przez zapętloną check().

 2
Author: Sam Brightman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-29 07:04:23

Zrobiłem ćwiczenie w klasie, jeśli chcesz prawdziwy przykład zmiennej warunkowej:

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"

int compteur = 0;
pthread_cond_t varCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_compteur;

void attenteSeuil(arg)
{
    pthread_mutex_lock(&mutex_compteur);
        while(compteur < 10)
        {
            printf("Compteur : %d<10 so i am waiting...\n", compteur);
            pthread_cond_wait(&varCond, &mutex_compteur);
        }
        printf("I waited nicely and now the compteur = %d\n", compteur);
    pthread_mutex_unlock(&mutex_compteur);
    pthread_exit(NULL);
}

void incrementCompteur(arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex_compteur);

            if(compteur == 10)
            {
                printf("Compteur = 10\n");
                pthread_cond_signal(&varCond);
                pthread_mutex_unlock(&mutex_compteur);
                pthread_exit(NULL);
            }
            else
            {
                printf("Compteur ++\n");
                compteur++;
            }

        pthread_mutex_unlock(&mutex_compteur);
    }
}

int main(int argc, char const *argv[])
{
    int i;
    pthread_t threads[2];

    pthread_mutex_init(&mutex_compteur, NULL);

    pthread_create(&threads[0], NULL, incrementCompteur, NULL);
    pthread_create(&threads[1], NULL, attenteSeuil, NULL);

    pthread_exit(NULL);
}
 1
Author: Central Thinking Unit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-04 08:40:57

Wydaje się być konkretną decyzją projektową, a nie koncepcyjną potrzebą.

Zgodnie z dokumentami pthreads powodem, dla którego mutex nie został rozdzielony, jest znaczna poprawa wydajności poprzez ich łączenie i oczekują, że ze względu na wspólne warunki wyścigowe, jeśli nie używasz mutex, to prawie zawsze będzie to zrobione i tak.

Https://linux.die.net/man/3/pthread_cond_wait

Cechy Muteksów i zmiennych warunkowych

Zasugerowano, aby nabycie i wydanie mutexu było / align= "left" / Został odrzucony, ponieważ jest to połączony charakter operacji, który w rzeczywistości ułatwia pracę w czasie rzeczywistym wdrożenia. Te implementacje mogą atomicznie przenosić wątek o wysokim priorytecie pomiędzy zmienną condition a mutex w sposób, który jest przejrzysty dla rozmówcy. Może to zapobiec dodatkowym przełączniki kontekstowe i zapewniają bardziej deterministyczne przejęcie mutex kiedy wątek oczekujący jest sygnalizowany. Tak więc uczciwość i priorytet kwestie mogą być rozpatrywane bezpośrednio przez dyscyplinę planowania. Ponadto aktualna operacja wait condition pasuje do istniejącej praktyka.

 0
Author: Catskul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-25 16:00:30