Lokalny czas trwania wątku to termin używany w odniesieniu do danych, które wydają się być globalnym lub statycznym czasem przechowywania (z punktu widzenia funkcji, które go używają), ale w rzeczywistości istnieje jedna kopia na wątek.
Dodaje do aktualnego automatycznego (istnieje podczas bloku / funkcji), statycznego (istnieje przez czas trwania programu) i dynamicznego (istnieje na stercie między alokacją a zwolnieniem).
Coś, co jest lokalne dla wątku, powstaje podczas tworzenia wątku i jest usuwane, gdy wątek się zatrzymuje.
Oto kilka przykładów.
Pomyśl o generatorze liczb losowych, w którym ziarno musi być utrzymywane na podstawie wątku. Użycie lokalnego ziarna wątku oznacza, że każdy wątek otrzymuje własną sekwencję liczb losowych, niezależną od innych wątków.
Jeśli twoje ziarno było lokalną zmienną w funkcji losowej, byłoby inicjalizowane za każdym razem, gdy ją wywołasz, dając ci za każdym razem tę samą liczbę. Gdyby był globalny, wątki interferowałyby ze sobą w sekwencjach.
Innym przykładem jest strtok
sytuacja, w której stan tokenizacji jest przechowywany na podstawie określonego wątku. W ten sposób pojedynczy wątek może mieć pewność, że inne wątki nie zepsują jego wysiłków związanych z tokenizacją, a jednocześnie będą w stanie utrzymać stan wielu wywołań strtok
- to w zasadzie powoduje, że strtok_r
(wersja bezpieczna dla wątków) jest zbędna.
Oba te przykłady pozwalają na istnienie zmiennej lokalnej wątku w funkcji, która jej używa. W kodzie wstępnie wątkowym byłaby to po prostu statyczna zmienna czasu przechowywania w ramach funkcji. W przypadku wątków jest to modyfikowane do czasu trwania lokalnego magazynu wątków.
Jeszcze innym przykładem może być coś podobnego errno
. Nie chcesz, aby oddzielne wątki modyfikowały się errno
po niepowodzeniu jednego z wywołań, ale zanim będziesz mógł sprawdzić zmienną, a mimo to chcesz mieć tylko jedną kopię na wątek.
Ta witryna zawiera rozsądny opis różnych specyfikacji czasu przechowywania.
strtok
.strtok
jest zepsuty nawet w środowisku jednowątkowym.r
oznacza to „re-entrant”, co nie ma nic wspólnego z bezpieczeństwem nici. Prawdą jest, że możesz sprawić, by niektóre rzeczy działały bezpiecznie wątkowo dzięki pamięci lokalnej wątkowej, ale nie możesz ich ponownie wprowadzić.strtok
wywoływać inne funkcje.while (something) { char *next = strtok(whatever); someFunction(next); // someFunction calls strtok }
Kiedy deklarujesz zmienną,
thread_local
każdy wątek ma swoją własną kopię. Kiedy odnosisz się do niego po nazwie, używana jest kopia skojarzona z bieżącym wątkiem. na przykładthread_local int i=0; void f(int newval){ i=newval; } void g(){ std::cout<<i; } void threadfunc(int id){ f(id); ++i; g(); } int main(){ i=9; std::thread t1(threadfunc,1); std::thread t2(threadfunc,2); std::thread t3(threadfunc,3); t1.join(); t2.join(); t3.join(); std::cout<<i<<std::endl; }
Ten kod zwróci „2349”, „3249”, „4239”, „4329”, „2439” lub „3429”, ale nigdy więcej. Każdy wątek ma swoją własną kopię
i
, do której jest przypisywana, zwiększana, a następnie drukowana. Działający wątekmain
ma również swoją własną kopię, która jest przypisywana na początku, a następnie pozostawiana bez zmian. Kopie te są całkowicie niezależne i każda ma inny adres.Jedynie nazwa jest pod tym względem wyjątkowa - jeśli bierzesz adres
thread_local
zmiennej, masz po prostu normalny wskaźnik do normalnego obiektu, który możesz swobodnie przekazywać między wątkami. na przykładthread_local int i=0; void thread_func(int*p){ *p=42; } int main(){ i=9; std::thread t(thread_func,&i); t.join(); std::cout<<i<<std::endl; }
Ponieważ adres
i
jest przekazywany do funkcjii
wątku, można przypisać kopię przynależności do wątku głównego, mimo że tak jestthread_local
. Ten program zwróci zatem „42”. Jeśli to zrobisz, musisz uważać, aby*p
nie uzyskać dostępu po wyjściu wątku, do którego należy, w przeciwnym razie otrzymasz wiszący wskaźnik i niezdefiniowane zachowanie, tak jak w każdym innym przypadku, gdy wskazany obiekt zostanie zniszczony.thread_local
zmienne są inicjalizowane „przed pierwszym użyciem”, więc jeśli nigdy nie są dotykane przez dany wątek, to niekoniecznie są one nigdy inicjalizowane. Ma to pozwolić kompilatorom na uniknięcie konstruowania każdejthread_local
zmiennej w programie dla wątku, który jest całkowicie niezależny i nie dotyka żadnego z nich. na przykładstruct my_class{ my_class(){ std::cout<<"hello"; } ~my_class(){ std::cout<<"goodbye"; } }; void f(){ thread_local my_class unused; } void do_nothing(){} int main(){ std::thread t1(do_nothing); t1.join(); }
W tym programie są 2 wątki: wątek główny i wątek utworzony ręcznie. Żaden wątek nie jest wywoływany
f
, więcthread_local
obiekt nigdy nie jest używany. W związku z tym nie jest określone, czy kompilator skonstruuje 0, 1 czy 2 wystąpieniamy_class
, a wyjściem może być „”, „hellohellogoodbyegoodbye” lub „hellogoodbye”.źródło
g()
wezwanie do początkuthreadFunc
, to wyjście będzie0304029
albo jakiś inny permutacji par02
,03
oraz04
. Oznacza to, że nawet jeśli 9 jest przypisane doi
przed utworzeniem wątków, wątki otrzymują świeżo skonstruowaną kopięi
gdziei=0
. Jeślii
jest przypisane za pomocąthread_local int i = random_integer()
, każdy wątek otrzymuje nową losową liczbę całkowitą.02
,03
,04
, mogą istnieć inne sekwencje, takie jak020043
Pamięć lokalna wątku jest pod każdym względem podobna do pamięci statycznej (= globalnej), z tą różnicą, że każdy wątek ma oddzielną kopię obiektu. Czas życia obiektu zaczyna się albo na początku wątku (dla zmiennych globalnych), albo przy pierwszej inicjalizacji (dla statystyki lokalnej bloku) i kończy się, gdy wątek się kończy (tj. Kiedy
join()
jest wywoływany).W konsekwencji tylko zmienne, które mogą być również zadeklarowane,
static
mogą być zadeklarowane jakothread_local
, tj. Zmienne globalne (a dokładniej: zmienne „w zakresie przestrzeni nazw”), statyczne składowe klas i zmienne statyczne bloku (w tym przypadkustatic
jest to implikowane).Na przykład załóżmy, że masz pulę wątków i chcesz wiedzieć, jak dobrze równoważono obciążenie pracą:
thread_local Counter c; void do_work() { c.increment(); // ... } int main() { std::thread t(do_work); // your thread-pool would go here t.join(); }
Spowoduje to wydrukowanie statystyk użycia wątku, np. W takiej implementacji:
struct Counter { unsigned int c = 0; void increment() { ++c; } ~Counter() { std::cout << "Thread #" << std::this_thread::id() << " was called " << c << " times" << std::endl; } };
źródło