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?
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?
Odpowiedzi:
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:
manipuluje tylko współdzielonymi strukturami danych w sposób gwarantujący bezpieczne wykonywanie przez wiele wątków w tym samym czasie.
Funkcja jest ponownie wprowadzana, jeśli:
może zostać przerwane w dowolnym momencie podczas wykonywania, a następnie bezpiecznie wywołane ponownie („wprowadzone ponownie”), zanim poprzednie wywołania zakończą się.
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.
Kluczem do uniknięcia nieporozumień jest to, że ponowne wejście odnosi się do wykonywania tylko jednego wątku. To koncepcja z czasów, gdy nie istniały żadne wielozadaniowe systemy operacyjne.
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;
}
t = *x
, połączenia swap()
, a następnie t
zostaną nadpisane, co prowadzi do nieoczekiwanych rezultatów.
swap(5, 6)
przerwania przez swap(1, 2)
. Po t=*x
, s=t_original
i t=5
. Teraz po przerwie s=5
i t=1
. Jednak zanim drugi swap
powróci, przywróci kontekst, tworząc t=s=5
. Teraz wracamy do pierwszego swap
z t=5 and s=t_original
i kontynuujemy po t=*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:
Funkcja bezpieczna dla wątków * może być wywoływana jednocześnie z wielu wątków, nawet jeśli wywołania używają danych współużytkowanych, ponieważ wszystkie odwołania do danych udostępnionych są serializowane.
ZA Reentrant funkcja może być również nazywane równocześnie z wielu wątków, ale tylko wtedy, gdy każde wywołanie używa swoich własnych danych.
Stąd a funkcja bezpieczna wątków jest zawsze ponowna, ale funkcja ponownie wprowadzana nie zawsze jest bezpieczna dla wątków.
W związku z tym mówi się o klasie ponownie wprowadzana, jeśli jej funkcje składowe mogą być bezpiecznie wywoływane z wielu wątków, o ile każdy wątek używa innej instancji klasy. Klasa jest bezpieczna dla wątków, jeśli jej funkcje składowe można bezpiecznie wywoływać z wielu wątków, nawet jeśli wszystkie wątki używają tego samego wystąpienia klasy.
ale także ostrzegają:
Uwaga: Terminologia w dziedzinie wielowątkowości nie jest całkowicie ustandaryzowana. POSIX używa definicji reentrant i thread-safe, które są nieco inne dla jego C API. Używając innych obiektowych bibliotek klas C ++ z Qt, upewnij się, że definicje są zrozumiałe.