Pytanie Threadsafe vs ponownie-uczestnik


Niedawno zadałem pytanie, z tytułem jako "Czy wątek malloc jest bezpieczny?", a wewnątrz tego, zapytałem: "Czy Malloc ponownie wchodzi na rynek?"

Miałem wrażenie, że wszyscy uczestnicy programu ponownie są zabezpieczeni wątkiem.

Czy to założenie jest błędne?


76
2018-05-13 08:43


pochodzenie




Odpowiedzi:


Funkcje ponownego wejścia nie opierają się na zmiennych globalnych, które są ujawnione w nagłówkach bibliotek C .. weź strtok () vs strtok_r () na przykład w C.

Niektóre funkcje wymagają miejsca do przechowywania "pracy w toku", funkcje ponownego wejścia pozwalają na określenie tego wskaźnika wewnątrz własnej pamięci wątku, a nie na globalnym. Ponieważ ta pamięć jest zarezerwowana tylko dla funkcji wywołującej, może zostać przerwana i ponownie wprowadzono (ponowne uczestnictwo) i ponieważ w większości przypadków wzajemne wykluczenie poza to, co funkcja wykonuje, nie jest wymagane, aby dział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 jest dziwakiem w objaśnieniu, jak to wszystko działa) :)

W skrócie, reentrant często oznacza wątek bezpieczny (jak w "używaj wersji tej samej funkcji, jeśli używasz wątków"), ale wątek bezpieczny nie zawsze oznacza ponowne wejście (lub odwrotnie). Kiedy patrzysz na bezpieczeństwo nici, konkurencja to jest to, o czym musisz myśleć. Jeśli musisz użyć funkcji blokowania i wzajemnego wykluczania, aby użyć funkcji, funkcja nie jest z natury bezpieczna dla wątków.

Ale nie wszystkie funkcje muszą zostać zbadane. malloc() nie musi być reentrantem, nie zależy od niczego poza zakresem punktu wejścia dla danego wątku (i sam jest bezpieczny w wątku).

Funkcje zwracające statycznie przydzielone wartości to nie bezpieczne nici bez użycia muteksu, futexu lub innego mechanizmu blokowania atomów. Jednak nie muszą być na stałe, jeśli nie zostaną przerwane.

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

Więc, jak widzisz, posiadanie wielu wątków używa tego bez pewnego rodzaju blokady, byłoby katastrofą ... ale nie ma potrzeby ponownego wchodzenia na rynek. Przekonasz się o tym, gdy dynamicznie przydzielana pamięć jest tabu na niektórych platformach wbudowanych.

W czysto funkcjonalnym programowaniu często pojawia się ponownie nie robi sugeruje, że wątek bezpieczny, zależałby od zachowania określonych lub anonimowych funkcji przekazywanych do punktu wejścia funkcji, rekursji itp.

Lepszym sposobem na umieszczenie "wątku bezpieczne" jest bezpieczny dla równoczesnego dostępu , co lepiej ilustruje potrzebę.


38
2018-05-13 08:55



Reentrant nie oznacza, że ​​wątek jest bezpieczny. Czyste funkcje oznaczają bezpieczeństwo gwintów. - Julio Guerra
Świetna odpowiedź Tim. Aby wyjaśnić, moje zrozumienie z "często" jest takie, że wątek bezpieczny nie oznacza ponownego wprowadzania, ale także ponowne wprowadzenie nie implikuje wątku bezpieczne. Czy byłbyś w stanie znaleźć przykład funkcji z ponownym założeniem, która jest nie wątek bezpieczny? - Riccardo
@ Tim Post "W skrócie, reentrant często oznacza" bezpieczny dla wątków "(jak w" używaj wersji tej samej funkcji, jeśli używasz wątków "), ale wątek bezpieczny nie zawsze oznacza ponowne wprowadzenie." qt mówi przeciwnie: "W związku z tym funkcja bezpieczna dla wątków jest zawsze powtarzalna, ale funkcja ponownego wprowadzania nie zawsze jest bezpieczna dla wątków." - 4pie0
i wikipedia mówi jeszcze coś innego: "Ta definicja ponownego wiązania różni się od definicji bezpieczeństwa gwintów w środowiskach wielowątkowych. Reedrantowy podprogram może osiągnąć bezpieczeństwo gwintów [1], ale sam reentrant może nie wystarczyć do zabezpieczenia wątków we wszystkich sytuacjach Odwrotnie, kod zabezpieczający wątek niekoniecznie musi być ciągły (...) " - 4pie0
@Riccardo: funkcje zsynchronizowane przez zmienne zmienne, ale nie pełne bariery pamięciowe do użycia z procedurami obsługi sygnałów / przerw, są zazwyczaj ponownie wprowadzane, ale wątkowo bezpieczne. - doynax


To zależy od definicji. Na przykład Qt używa następujące:

  • Funkcja wątkowa bezpieczna * może być wywoływana jednocześnie z wielu wątków, nawet jeśli wywołania używają wspólnych danych, ponieważ wszystkie odwołania do udostępnionych danych są serializowane.

  • ZA reentrant Funkcja może być również wywoływana jednocześnie z wielu wątków, ale tylko wtedy, gdy każde wywołanie używa własnych danych.

Stąd, a Wątek bezpieczny funkcja jest zawsze reentrant, ale a reentrant funkcja nie zawsze jest bezpieczna dla wątków.

Mówi się, że jest to klasa reentrant jeśli jego funkcje członkowskie można bezpiecznie wywoływać z wielu wątków, o ile każdy wątek używa innej instancji klasy. Klasa jest Wątek bezpieczny jeżeli jego funkcje członkowskie 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 domenie wielowątkowości nie jest w pełni znormalizowana. POSIX używa definicji reentrant i thread-safe, które są nieco inne dla C API. Podczas korzystania z innych bibliotek obiektowych C ++ klasy Qt, upewnij się, że definicje są zrozumiałe.


54
2018-05-13 09:08



Ta definicja reentrantu jest zbyt silna. - qweruiop
Downvote. Funkcja bezpieczna dla wątków NIE jest zawsze taka sama. - SandBag_1996
Funkcja jest zarówno wielokrotna, jak i wątkowo bezpieczna, jeśli nie używa żadnego globalnego / statycznego var. Wątek bezpieczny: kiedy wiele wątków wykonuje twoją funkcję w tym samym czasie, czy jest jakaś rasa? Jeśli używasz globalnego var, użyj blokady, aby go chronić. więc jest bezpieczny dla wątków. reentrant: jeśli podczas wykonywania funkcji pojawia się sygnał i ponownie wywołujesz funkcję w sygnale, czy jest to bezpieczne? w takim przypadku nie ma wielu wątków. Najlepiej, jeśli nie użyjesz żadnego statycznego / globalnego var, aby stał się on reentrantem, lub jak w przykładzie 3. - keniee van


TL; DR: Funkcja może być ciągła, bezpieczna dla wątków, jedna lub żadna.

Artykuły z Wikipedii dla bezpieczeństwo gwintu i reentrancy są warte przeczytania. Oto kilka cytatów:

Funkcja to Wątek bezpieczny gdyby:

manipuluje tylko współdzielonymi strukturami danych w   sposób gwarantujący bezpieczne wykonanie przez wielu   wątki w tym samym czasie.

Funkcja to reentrant gdyby:

można go przerwać w dowolnym momencie podczas jego wykonywania   a następnie bezpiecznie przywołany ponownie ("ponownie wprowadzono") przed jego   poprzednie wywołania zakończone.

Jako przykłady możliwego przywrócenia, Wikipedia podaje przykład funkcji zaprojektowanej do wywoływania przez systemowe przerwania: załóżmy, że jest już uruchomiona, gdy nastąpi inne przerwanie. Ale nie myśl, że jesteś bezpieczny tylko dlatego, że nie kodujesz przerwań systemowych: możesz mieć problemy z przywracaniem w programie jednowątkowym, jeśli używasz funkcji zwrotnych lub rekursywnych.

Kluczem do uniknięcia zamieszania jest to, co odnosi się do reentrant   wykonuje tylko jeden wątek. Jest to koncepcja z czasów, kiedy   nie istniały wielozadaniowe systemy operacyjne.

Przykłady

(Nieznacznie zmodyfikowany z artykułów Wikipedii)

Przykład 1: nie jest bezpieczny dla wątków, nie jest powtórny

/* 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: bezpieczny dla wątków, bez ponownego wprowadzania

/* 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: nie jest bezpieczny dla wątków, ponowne wprowadzenie

/* 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: bezpieczny dla wątków, powtórny

/* 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;
}

53
2017-10-30 22:34



Wiem, że nie powinienem komentować tylko po to, by podziękować, ale jest to jedna z najlepszych ilustracji przedstawiających różnice pomiędzy funkcjami ponownego wrzucania i bezpieczeństwa wątków. W szczególności użyliście bardzo zwięzłych jasnych terminów i wybrali świetną przykładową funkcję, która rozróżnia 4 kategorie. Więc dziękuję! - ryyker
Funkcja jest zarówno wielokrotna, jak i wątkowo bezpieczna, jeśli nie używa żadnego globalnego / statycznego var. Wątek bezpieczny: kiedy wiele wątków wykonuje twoją funkcję w tym samym czasie, czy jest jakaś rasa? Jeśli używasz globalnego var, użyj blokady, aby go chronić. więc jest bezpieczny dla wątków. reentrant: jeśli podczas wykonywania funkcji pojawia się sygnał i ponownie wywołujesz funkcję w sygnale, czy jest to bezpieczne? w takim przypadku nie ma wielu wątków. nie użyjesz żadnego zmiennego statycznego / globalnego, aby był ponownie ... - keniee van
Wydaje mi się, że przykład 3 nie jest reentrantem: jeśli funkcja obsługi sygnału przerywa po t = *x, połączenia swap(), następnie t zostanie nadpisany, co doprowadzi do nieoczekiwanych rezultatów. - rom1v