2008-12-09 16 views

Odpowiedz

21

Kod powtórkowy nie ma stanu w jednym punkcie. Możesz wywołać kod, gdy coś jest wykonywane w kodzie. Jeśli kod używa stanu globalnego, jedno połączenie może w sposób jawny nadpisać stan globalny, przerywając obliczenia w drugim wywołaniu.

Kod bezpieczeństwa wątku jest kodowany bez warunków wyścigu lub innych problemów z współbieżnością. Warunkiem wyścigu jest sytuacja, w której kolejność, w której dwa wątki coś robią, wpływa na obliczenia. Typową kwestią współbieżności jest sytuacja, w której zmiana w udostępnianej strukturze danych może zostać częściowo zakończona i pozostawiona w niespójnym stanie. Aby tego uniknąć, musisz użyć mechanizmów kontroli współbieżności, takich jak semafory muteksów, aby zapewnić, że nic innego nie będzie mogło uzyskać dostępu do struktury danych, dopóki operacja nie zostanie zakończona.

Na przykład fragment kodu może nie być ponownie wprowadzany, ale wątkowo bezpieczny, jeśli jest chroniony zewnętrznie przez muteks, ale nadal ma globalną strukturę danych, w której stan musi być spójny przez cały czas trwania połączenia. W takim przypadku ten sam wątek może zainicjować oddzwonienie do procedury, a jednocześnie jest chroniony przez zewnętrzny gruboziarnisty muteks. Jeśli oddzwanianie wystąpiło w ramach procedury niezarejestrowanej, połączenie mogło opuścić strukturę danych w stanie, który mógłby przerwać obliczenia z punktu widzenia dzwoniącego.

Kawałek kodu może być ponownie wprowadzony, ale nie może być wątkowo bezpieczny, jeśli może dokonać nieatomowej zmiany w udostępnionej (i udostępnianej) strukturze danych, która może zostać przerwana w trakcie aktualizacji, pozostawiając strukturę danych w niespójnym stanie. W tym przypadku inny wątek, który uzyska dostęp do struktury danych, może zostać zmieniony przez zmienioną strukturę danych i awarię lub wykonać operację powodującą uszkodzenie danych.

+3

Twój drugi przykład nie brzmi dla mnie ponownie. Jeśli zmiana może zostać przerwana, pozostawiając niespójny stan, a sygnał pojawia się w tym punkcie, a funkcja obsługi tego sygnału wywołuje tę funkcję, wówczas zwykle zaczyna bum. To kwestia ponownego wejścia, a nie kwestia bezpieczeństwa wątków. –

+1

Masz rację - jak mówisz poniżej, musiałbyś również wyłączyć sygnały, aby ten ostatni był skutecznym powrotem. – ConcernedOfTunbridgeWells

+0

@ConcernedOfTunbridgeWells, jeśli func używa stosu wewnątrz, istnieje duża szansa, że ​​ten func nie zostanie ponownie wprowadzony. Czemu? – Alcott

8

Artykuł ten mówi:

„funkcja może być albo wklęsłego, bezpieczny wątku, zarówno, czy też nie.”

mówi również:

"funkcje dla wklęsłego są thread-niebezpieczne".

Widzę, jak to może powodować zamieszanie. Znaczy to, że standardowe funkcje udokumentowane jako niewymagane do ponownego wprowadzenia nie muszą być również bezpieczne dla wątków, co jest prawdą w bibliotekach POSIX iirc (i POSIX deklaruje, że jest to prawdziwe również w bibliotekach ANSI/ISO, mając ISO brak koncepcji nici, a więc brak koncepcji bezpieczeństwa nici). Innymi słowy, "jeśli jakaś funkcja mówi, że jest nie-reentrant, wtedy mówi, że jest również niebezpieczna dla wątków". To nie jest logiczna konieczność, to tylko konwencja.

Oto pseudo-kod, który jest bezpieczny dla wątków (cóż, istnieje wiele możliwości wywołań zwrotnych w celu utworzenia zakleszczenia z powodu inwersji blokowania, ale załóżmy, że dokumentacja zawiera wystarczającą ilość informacji, aby użytkownicy mogli tego uniknąć), ale nie powtórnie .Ma to zwiększyć globalny licznik oraz wykonywać oddzwanianie:

take_global_lock(); 
int i = get_global_counter(); 
do_callback(i); 
set_global_counter(i+1); 
release_global_lock(); 

Jeśli oddzwaniania tę procedurę jeszcze raz, co skutkuje innym zwrotnego, a następnie oba poziomy zwrotnego dostanie ten sam parametr (co może być OK, w zależności od API), ale licznik zostanie inkrementowany tylko jeden raz (co prawie na pewno nie jest interfejsem API, który chcesz, więc musiałby zostać zbanowany).

Zakładamy oczywiście, że zamek jest rekursywny. Jeśli blokada jest nierekurencyjna, to oczywiście kod jest nierezydentalny, ponieważ zablokowanie po raz drugi nie zadziała.

Oto niektóre pseudo-code, która jest „słabo ponownej instalacji” ale nie bezpieczny wątku:

int i = get_global_counter(); 
do_callback(i); 
set_global_counter(get_global_counter()+1); 

Teraz jest w porządku, aby wywołać funkcję z zwrotnego, ale nie jest to bezpieczne, aby wywołać funkcję jednoczesnego z różnych wątków. Nie jest również bezpiecznie wywoływać go z kontrolera sygnału, ponieważ ponowne wejście od obsługi sygnału może również przerwać liczenie, jeśli sygnał wystąpi we właściwym czasie. Tak więc kod jest niezarejestrowany według właściwej definicji.

Oto kod, który prawdopodobnie jest w pełni ponownie wprowadzony (chyba, że ​​standard rozróżnia między reentrantem a "nieprzerywalnym sygnałami" i nie jestem pewien, gdzie to się dzieje), ale wciąż nie jest wątkiem bezpieczne:

int i = get_global_counter(); 
do_callback(i); 
disable_signals(); // and any other kind of interrupts on your system 
set_global_counter(get_global_counter()+1); 
restore_signal_state(); 

W aplikacji jednowątkowej wszystko jest w porządku, zakładając, że system operacyjny obsługuje wyłączanie wszystkiego, co musi być wyłączone. Zapobiega ponownemu entrancy w punkcie krytycznym. W zależności od tego, jak wyłączone są sygnały, może być bezpiecznie wywołać z procedury obsługi sygnału, chociaż w tym konkretnym przypadku problem z przekazywaniem wywołania zwrotnego jest taki sam dla osobnych wywołań. Jednak nadal może pójść źle w wielowątkowych.

W praktyce, zabezpieczenie nie-wątkowe często implikuje brak ponownego wprowadzenia, ponieważ (nieformalnie) wszystko, co może pójść nie tak ze względu na przerwanie wątku przez program planujący, oraz funkcję wywoływaną ponownie z innego wątku, może również pójdzie źle, jeśli wątek zostanie przerwany przez sygnał, a funkcja zostanie ponownie wywołana z procedury obsługi sygnału. Ale wtedy "poprawka", aby zapobiec sygnałom (wyłączając je) różni się od "poprawki", aby zapobiec współbieżności (zwykle blokady). Jest to w najlepszym razie regułą.

Zauważyłem, że tutaj zostały podane globale, ale dokładnie te same uwagi miałyby zastosowanie, gdyby funkcja przyjęła jako parametr wskaźnik do licznika i blokady. Chodzi o to, że różne przypadki byłyby niebezpieczne dla wątków lub nie były ponownie wprowadzane po wywołaniu z tym samym parametrem, a nie po wywołaniu w ogóle.