Co i dlaczego rekurencyjnego muteksu nie powinno być tak skomplikowaną rzeczą opisaną w przyjętej odpowiedzi.
Chciałbym spisać swoje zrozumienie po pewnym kopaniu w sieci.
Po pierwsze, powinieneś zdać sobie sprawę, że kiedy mówisz o muteksie , z pewnością chodzi również o koncepcje wielowątkowe. (mutex jest używany do synchronizacji. Nie potrzebuję muteksu, jeśli mam tylko 1 wątek w moim programie)
Po drugie, powinieneś znać różnicę między normalnym muteksem a rekurencyjnym muteksem .
Cytat z APUE :
(Rekurencyjny mutex to a) Typ muteksu, który pozwala temu samemu wątkowi na wielokrotne blokowanie go bez uprzedniego odblokowania.
Kluczowa różnica polega na tym, że w ramach tego samego wątku ponowne zablokowanie blokady rekurencyjnej nie prowadzi do zakleszczenia ani nie powoduje zablokowania wątku.
Czy to oznacza, że blokada recuzyjna nigdy nie powoduje zakleszczenia?
Nie, nadal może powodować zakleszczenie jako normalny muteks, jeśli zablokowałeś go w jednym wątku bez odblokowania go i spróbujesz zablokować go w innych wątkach.
Zobaczmy jakiś kod jako dowód.
- normalny mutex z zakleszczeniem
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t lock;
void * func1(void *arg){
printf("thread1\n");
pthread_mutex_lock(&lock);
printf("thread1 hey hey\n");
}
void * func2(void *arg){
printf("thread2\n");
pthread_mutex_lock(&lock);
printf("thread2 hey hey\n");
}
int main(){
pthread_mutexattr_t lock_attr;
int error;
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
if(error){
perror(NULL);
}
pthread_mutex_init(&lock, &lock_attr);
pthread_t t1, t2;
pthread_create(&t1, NULL, func1, NULL);
pthread_create(&t2, NULL, func2, NULL);
pthread_join(t2, NULL);
}
wynik:
thread1
thread1 hey hey
thread2
typowy przykład zakleszczenia, nie ma problemu.
- rekurencyjny mutex z zakleszczeniem
Po prostu odkomentuj tę linię
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
i skomentuj drugą.
wynik:
thread1
thread1 hey hey
thread2
Tak, rekurencyjny mutex może również powodować zakleszczenie.
- normalny mutex, zablokuj ponownie w tym samym wątku
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t lock;
void func3(){
printf("func3\n");
pthread_mutex_lock(&lock);
printf("func3 hey hey\n");
}
void * func1(void *arg){
printf("thread1\n");
pthread_mutex_lock(&lock);
func3();
printf("thread1 hey hey\n");
}
void * func2(void *arg){
printf("thread2\n");
pthread_mutex_lock(&lock);
printf("thread2 hey hey\n");
}
int main(){
pthread_mutexattr_t lock_attr;
int error;
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
if(error){
perror(NULL);
}
pthread_mutex_init(&lock, &lock_attr);
pthread_t t1, t2;
pthread_create(&t1, NULL, func1, NULL);
sleep(2);
pthread_create(&t2, NULL, func2, NULL);
pthread_join(t2, NULL);
}
wynik:
thread1
func3
thread2
Impas w thread t1
, w func3
.
(Używam, sleep(2)
aby łatwiej zobaczyć, że zakleszczenie jest spowodowane najpierw ponownym zablokowaniem func3
)
- rekurencyjny mutex, blokuj ponownie w tym samym wątku
Ponownie odkomentuj rekursywną linię muteksu i skomentuj drugą linię.
wynik:
thread1
func3
func3 hey hey
thread1 hey hey
thread2
Impas w thread t2
, w func2
. Widzieć? func3
kończy się i wychodzi, ponowne zablokowanie nie blokuje nici ani nie prowadzi do zakleszczenia.
Więc ostatnie pytanie, dlaczego tego potrzebujemy?
Dla funkcji rekurencyjnej (wywoływanej w programach wielowątkowych i chcesz chronić niektóre zasoby / dane).
Np. Masz program wielowątkowy i wywołujesz funkcję rekurencyjną w wątku A. Masz pewne dane, które chcesz chronić w tej funkcji rekurencyjnej, więc używasz mechanizmu mutex. Wykonywanie tej funkcji jest sekwencyjne w wątku A, więc na pewno ponownie zablokowałbyś muteks w rekurencji. Użyj normalnego muteksu powoduje zakleszczenia. I resursive mutex jest wymyślony, aby rozwiązać ten problem.
Zobacz przykład z zaakceptowanej odpowiedzi.
Kiedy używać rekurencyjnego muteksu? .
Wikipedia bardzo dobrze wyjaśnia rekurencyjny muteks. Na pewno warto poczytać. Wikipedia: Reentrant_mutex