Niedawno zadałem pytanie o tytule „Czy wątek malloc jest bezpieczny?” , aw środku zapytałem: „Czy Malloc jest ponownie wprowadzony?”
Odniosłem wrażenie, że wszyscy ponownie wchodzący są bezpieczni dla wątków.
Czy to założenie jest błędne?
źródło
Niedawno zadałem pytanie o tytule „Czy wątek malloc jest bezpieczny?” , aw środku zapytałem: „Czy Malloc jest ponownie wprowadzony?”
Odniosłem wrażenie, że wszyscy ponownie wchodzący są bezpieczni dla wątków.
Czy to założenie jest błędne?
Funkcje re-entrant nie opierają się na zmiennych globalnych, które są uwidocznione w nagłówkach biblioteki C. Weź strtok () vs strtok_r () na przykład w C.
Niektóre funkcje wymagają miejsca na przechowywanie „pracy w toku”, a funkcje ponownego wejścia pozwalają określić ten wskaźnik we własnej pamięci wątku, a nie w globalnej. Ponieważ ta pamięć jest wyłączna dla funkcji wywołującej, może zostać przerwana i ponownie wprowadzona (ponowne wejście), a ponieważ w większości przypadków wzajemne wykluczenie poza to, co implementuje funkcja, nie jest wymagane, aby to zadziałało, często uważa się je za bezpieczny wątek . Nie jest to jednak gwarantowane z definicji.
errno jest jednak nieco innym przypadkiem w systemach POSIX (i wydaje się dziwacznym wyjaśnieniem, jak to wszystko działa) :)
Krótko mówiąc, ponowne wejście często oznacza bezpieczne dla wątków (tak jak w przypadku „użyj wersji tej funkcji, jeśli używasz wątków”), ale bezpieczeństwo wątków nie zawsze oznacza ponowne wejście (lub odwrotnie). Kiedy patrzysz na bezpieczeństwo wątków, musisz pomyśleć o współbieżności . Jeśli musisz zapewnić środki do blokowania i wzajemnego wykluczania, aby używać funkcji, funkcja nie jest z natury bezpieczna dla wątków.
Ale nie wszystkie funkcje muszą być sprawdzane. malloc()
nie ma potrzeby ponownego wchodzenia, nie zależy od niczego poza zakresem punktu wejścia dla danego wątku (i sam jest bezpieczny dla wątków).
Funkcje, które zwracają wartości przydzielone statycznie, nie są bezpieczne dla wątków bez użycia mechanizmu mutex, futex lub innego atomowego mechanizmu blokującego. Jednak nie muszą ponownie wchodzić, jeśli nie mają być przerywane.
to znaczy:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Tak więc, jak widać, korzystanie z tego przez wiele wątków bez jakiegoś rodzaju blokowania byłoby katastrofą ... ale ponowne wejście nie ma na celu. Napotkasz to, gdy dynamicznie przydzielana pamięć jest tabu na niektórych wbudowanych platformach.
W programowaniu czysto funkcjonalnym ponowne wejście często nie oznacza bezpieczeństwa wątków, zależałoby to od zachowania zdefiniowanych lub anonimowych funkcji przekazanych do punktu wejścia funkcji, rekurencji itp.
Lepszym sposobem na zapewnienie bezpieczeństwa wątków jest bezpieczeństwo w przypadku dostępu równoległego , co lepiej ilustruje tę potrzebę.
TL; DR: Funkcja może być ponownie wprowadzana, bezpieczna wątkowo, obie lub żadne.
Warto przeczytać artykuły Wikipedii dotyczące bezpieczeństwa wątków i ponownego wejścia na serwer . Oto kilka cytatów:
Funkcja jest bezpieczna wątkowo, jeśli:
Funkcja jest ponownie wprowadzana, jeśli:
Jako przykłady możliwego ponownego wejścia Wikipedia podaje przykład funkcji przeznaczonej do wywołania przez przerwania systemowe: załóżmy, że jest już uruchomiona, gdy nastąpi kolejne przerwanie. Ale nie myśl, że jesteś bezpieczny tylko dlatego, że nie kodujesz z przerwaniami systemowymi: możesz mieć problemy z ponownym wejściem w programie jednowątkowym, jeśli używasz wywołań zwrotnych lub funkcji rekurencyjnych.
Przykłady
(Nieznacznie zmodyfikowane na podstawie artykułów Wikipedii)
Przykład 1: nie jest bezpieczny dla wątków, nie jest ponownie wprowadzany
/* As this function uses a non-const global variable without any precaution, it is neither reentrant nor thread-safe. */ int t; void swap(int *x, int *y) { t = *x; *x = *y; *y = t; }
Przykład 2: bezpieczne dla wątków, niewprowadzane ponownie
/* We use a thread local variable: the function is now thread-safe but still not reentrant (within the same thread). */ __thread int t; void swap(int *x, int *y) { t = *x; *x = *y; *y = t; }
Przykład 3: brak bezpieczeństwa wątków, ponowne wejście
/* We save the global state in a local variable and we restore it at the end of the function. The function is now reentrant but it is not thread safe. */ int t; void swap(int *x, int *y) { int s; s = t; t = *x; *x = *y; *y = t; t = s; }
Przykład 4: bezpieczeństwo wątków, ponowne wejście
/* We use a local variable: the function is now thread-safe and reentrant, we have ascended to higher plane of existence. */ void swap(int *x, int *y) { int t; t = *x; *x = *y; *y = t; }
źródło
t = *x
, połączeniaswap()
, a następniet
zostaną nadpisane, co prowadzi do nieoczekiwanych rezultatów.swap(5, 6)
przerwania przezswap(1, 2)
. Pot=*x
,s=t_original
it=5
. Teraz po przerwies=5
it=1
. Jednak zanim drugiswap
powróci, przywróci kontekst, tworząct=s=5
. Teraz wracamy do pierwszegoswap
zt=5 and s=t_original
i kontynuujemy pot=*x
. Tak więc funkcja wydaje się być ponownie wprowadzana. Pamiętaj, że każde wywołanie otrzymuje własną kopięs
alokacji na stosie.To zależy od definicji. Na przykład Qt używa następującego:
ale także ostrzegają:
źródło