Wywołanie pthread_cond_signal bez blokowania muteksu

85

Czytałem gdzieś, że powinniśmy zablokować muteks przed wywołaniem pthread_cond_signal i odblokować muteks po wywołaniu go:

Funkcja pthread_cond_signal () służy do sygnalizowania (lub wybudzania) innego wątku, który oczekuje na zmienną warunku. Powinien zostać wywołany po zablokowaniu muteksu i musi odblokować muteks, aby procedura pthread_cond_wait () została zakończona.

Moje pytanie brzmi: czy nie jest w porządku wywoływanie metod pthread_cond_signal lub pthread_cond_broadcast bez blokowania muteksu?

B Faley
źródło

Odpowiedzi:

143

Jeśli nie zablokujesz muteksu w ścieżce kodowej, która zmienia stan i sygnały, możesz utracić wybudzenia. Rozważ tę parę procesów:

Proces A:

pthread_mutex_lock(&mutex);
while (condition == FALSE)
    pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

Proces B (nieprawidłowy):

condition = TRUE;
pthread_cond_signal(&cond);

Następnie rozważ możliwość przeplotu instrukcji, gdzie conditionzaczyna się jako FALSE:

Process A                             Process B

pthread_mutex_lock(&mutex);
while (condition == FALSE)

                                      condition = TRUE;
                                      pthread_cond_signal(&cond);

pthread_cond_wait(&cond, &mutex);

conditionJest teraz TRUE, ale jest zablokowany Proces A czeka na zmiennej warunkowej - to brakowało sygnał wzbudzenia. Jeśli zmienimy Proces B, aby zablokować muteks:

Proces B (poprawny):

pthread_mutex_lock(&mutex);
condition = TRUE;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

... wtedy powyższe nie może wystąpić; przebudzenie nigdy nie zostanie pominięte.

(Zauważ, że możesz faktycznie przenieść to pthread_cond_signal()samo po znaku pthread_mutex_unlock(), ale może to spowodować mniej optymalne planowanie wątków i koniecznie zablokowałeś muteks już w tej ścieżce kodu z powodu zmiany samego warunku).

kawiarnia
źródło
4
@Nemo: Tak, w "prawidłowej" ścieżce pthread_signal_cond() można przesunąć po odblokowaniu mutexa, chociaż prawdopodobnie lepiej tego nie robić. Być może bardziej poprawne jest stwierdzenie, że w momencie, w którym dzwonisz pthread_signal_cond(), będziesz musiał już zablokować muteks, aby zmodyfikować sam warunek.
kawiarnia
@Nemo: Tak, kod jest uszkodzony, dlatego jest oznaczony jako „niepoprawny” - z mojego doświadczenia wynika, że ​​ludzie, którzy zadają to pytanie, często rozważają pozostawienie blokady całkowicie na ścieżce sygnalizacyjnej. Być może twoje doświadczenie jest inne.
kawiarnia
10
Na ile to warte, wydaje się, że pytanie, czy odblokować przed, czy po sygnale, jest w rzeczywistości nietrywialne. Odblokowanie po zapewnia, że ​​wątek o niższym priorytecie nie będzie w stanie ukraść zdarzenia z wydarzenia o wyższym priorytecie, ale jeśli nie używasz priorytetów, odblokowanie przed sygnałem faktycznie zmniejszy liczbę wywołań systemowych / przełączników kontekstu i poprawić ogólną wydajność.
R .. GitHub STOP HELPING ICE
1
@R .. Masz to od tyłu. Odblokowanie przed sygnałem zwiększa liczbę wywołań systemowych i zmniejsza ogólną wydajność. Jeśli zasygnalizujesz przed odblokowaniem, implementacja wie, że sygnał nie może obudzić wątku (ponieważ każdy wątek zablokowany na cv potrzebuje muteksu, aby przejść do przodu), pozwalając na użycie szybkiej ścieżki. Jeśli zasygnalizujesz po odblokowaniu, zarówno odblokowanie, jak i sygnał mogą obudzić wątki, co oznacza, że ​​obie są kosztowne.
David Schwartz
1
@Alceste_: Zobacz ten tekst w POSIX : "Kiedy wątek czeka na zmienną warunku, po określeniu konkretnego muteksu do operacji pthread_cond_timedwait()lub pthread_cond_wait(), tworzone jest dynamiczne wiązanie między tym muteksem a zmienną warunku, które pozostaje w co najmniej jeden wątek jest zablokowany w zmiennej warunku. W tym czasie efekt próby oczekiwania przez dowolny wątek na tę zmienną warunku przy użyciu innego muteksu jest niezdefiniowany.
caf
51

Zgodnie z tą instrukcją:

Te pthread_cond_broadcast()lub pthread_cond_signal()funkcje mogą być wywołana przez wątek, czy obecnie jest właścicielem mutex że wątki rozmów pthread_cond_wait() lub pthread_cond_timedwait()nie powiązanych ze zmienną stan podczas ich czeka; jednak, jeśli wymagane jest przewidywalne zachowanie planowania, wówczas ten mutex powinien być zablokowany przez wywołanie wątku pthread_cond_broadcast()lub pthread_cond_signal().

Znaczenie instrukcji przewidywalnego zachowania harmonogramu zostało wyjaśnione przez Dave'a Butenhof (autor książki Programming with POSIX Threads ) na comp.programming.threads i jest dostępne tutaj .

przestępczość lodowa
źródło
6
+1 za łączenie poczty od Dave'a Butenhofa. Zawsze sam się nad tym zastanawiałem, teraz już wiem ... Nauczyłem się dziś czegoś ważnego. Dzięki.
Nils Pipenbrinck
Jeśli wymagane jest przewidywalne zachowanie związane z planowaniem, umieść instrukcje w jednym wątku w żądanej kolejności lub użyj priorytetów wątków.
Kaz
7

caf, w przykładowym kodzie Proces B modyfikuje conditionbez blokowania mechanizmu mutex. Jeśli Proces B po prostu zablokował muteks podczas tej modyfikacji, a następnie nadal odblokował muteks przed wywołaniempthread_cond_signal , nie byłoby problemu - czy mam rację?

Intuicyjnie wierzę, że stanowisko kawiarni jest poprawne: dzwonienie pthread_cond_signalbez posiadania blokady mutex to zły pomysł. Ale przykład kawiarni nie jest w rzeczywistości dowodem na poparcie tego stanowiska; jest to po prostu dowód na poparcie znacznie słabszego (praktycznie oczywistego) stanowiska, że ​​złym pomysłem jest modyfikowanie wspólnego stanu chronionego muteksem, chyba że najpierw zablokujesz ten muteks.

Czy ktoś może podać przykładowy kod, w którym wywołanie, pthread_cond_signalpo którym następuje, pthread_mutex_unlockdaje poprawne zachowanie, ale wywołanie, pthread_mutex_unlockpo którym następuje, pthread_cond_signaldaje nieprawidłowe zachowanie?

Quuxplusone
źródło
3
Właściwie myślę, że moje pytanie jest duplikatem tego , a odpowiedź brzmi: „W porządku, możesz całkowicie wywołać pthread_cond_signal bez posiadania blokady mutex. Nie ma problemu z poprawnością. Ale w typowych implementacjach przegapisz sprytną optymalizację głęboko wewnątrz pthreads, więc nieco lepiej jest wywołać pthread_cond_signal, wciąż trzymając blokadę. "
Quuxplusone
Zrobiłem to w ostatnim akapicie mojej odpowiedzi.
kawiarnia
Masz tutaj dobry scenariusz: stackoverflow.com/a/6419626/1284631 Zauważ, że nie twierdzi, że zachowanie jest nieprawidłowe, przedstawia tylko przypadek, w którym zachowanie może nie być zgodne z oczekiwaniami.
user1284631
Możliwe jest utworzenie przykładowego kodu, w którym wywołanie pthread_cond_signalafter pthread_mutex_unlockmoże spowodować utratę wybudzenia, ponieważ sygnał jest przechwytywany w „złym” wątku, takim, który został zablokowany po zobaczeniu zmiany w predykacie. Jest to problem tylko wtedy, gdy ta sama zmienna warunku może być używana dla więcej niż jednego predykatu i nie używasz pthread_cond_broadcast, co i tak jest rzadkim i delikatnym wzorcem.
David Schwartz