C ++ Wątek dołączania

C ++ Wątek dołączania
Wątki są jak podprogramy programu. I tak uciekliby niezależnie, chyba że zostali połączeni. Połączenie oznacza, że ​​jeden wątek będzie działał, dopóki nie osiągnie określonego punktu, a następnie zatrzymuje się i czeka, aż inny wątek zakończy wykonanie (do końca); Zanim kontynuuje własną realizację. W punkcie, w którym zatrzymuje się pierwszy wątek, jest oświadczenie o połączeniu. Jeden wątek może wywołać inny wątek. Łączy wątek, który łączy się. Nazywany wątek nie dołącza.

Dołączenie istnieje, ponieważ wątki muszą się ze sobą komunikować. Po przeprowadzeniu zmiany nazywany wątek może zmienić wartość zmiennej globalnej, do której wymaga dostępu wątek wywołujący. Jest to forma synchronizacji.

W tym artykule wyjaśniono dwa sposoby dołączenia do wątków. Zaczyna się od ilustracji tego, czym jest wątek.

Treść artykułu

  • Nitka
  • Dołączenie do wątku
  • przyszłość :: get ()
  • Wniosek

Nitka

Rozważ następujący program:

#włączać
za pomocą przestrzeni nazw Std;
void fn2 ()
Cout << "function 2" << '\n';

void fn1 ()
Cout << "function 1" << '\n';

int main ()

/ * niektóre stwierdzenia */
powrót 0;

fn1 (), fn2 () i main () to funkcje najwyższego poziomu, chociaż main () jest kluczową funkcją. Trzy wątki można uzyskać z tych trzech funkcji najwyższego poziomu. Poniższy bardzo prosty krótki program to naturalny wątek:

#włączać
za pomocą przestrzeni nazw Std;
int main ()

/ * niektóre stwierdzenia */
powrót 0;

Funkcja main () zachowuje się jak wątek. Można to uznać za główny wątek. Nie musi być zamknięty w żadnym instancji z klasy wątków. Tak więc poprzedni program z funkcjami najwyższego poziomu, który zawiera funkcję main (), jest nadal jednym wątkiem. Poniższy program pokazuje, w jaki sposób dwie funkcje, fn1 () i fn2 () można przekonwertować na wątki, ale bez instrukcji łączenia:

#włączać
#włączać
#włączać
za pomocą przestrzeni nazw Std;
String GLOBL1 = String („SO,”);
String GLOBL2 = String ("czy to!");
void fn2 (string st2)
String GLOBL = GLOBL1 + ST2;
Cout << globl << endl;

void fn1 (string st1)
globl1 = "tak. „ + ST1;
Wątek THR2 (FN2, GLOBL2);

int main ()

Wątek thr1 (FN1, GLOBL1);
/ * niektóre stwierdzenia */
powrót 0;

Program zaczyna się od włączenia biblioteki iostream dla obiektu Cout. Następnie uwzględniona jest biblioteka wątków. Uwzględnienie biblioteki wątków jest koniecznością; tak aby programista po prostu utworzył instancję obiektu wątku z klasy wątku za pomocą funkcji najwyższego poziomu, z wyjątkiem funkcji Main ().

Następnie uwzględniono bibliotekę ciągów. Ta biblioteka upraszcza użycie literałów smyczkowych. Pierwsze stwierdzenie w programie nalega, aby każda użyta nazwa pochodzi z standardowej przestrzeni nazw C ++, chyba że wskazano inaczej.

Kolejne dwa stwierdzenia deklarują dwa globalne obiekty ciągów z ich literałami. Obiekty strunowe nazywane są GLOBL1 i GLOBL2. Istnieje funkcja fn1 () i funkcja fn2 (). Nazwa funkcji fn2 () będzie używana jako jeden z argumentów do instancji wątku, thr2, z klasy wątku. Gdy funkcja jest utworzona w ten sposób, funkcja jest wywoływana; i wykonuje. Kiedy wywoływana jest funkcja fn2 (), łączy literały strunowe GLOBL1 i GLOBL2, aby mieć „więc, czy to, czy to!". GLOBL2 jest argumentem fn2 ().

Nazwa funkcji fn1 () jest używana jako argument do tworzenia instancji wątku, thr1, z klasy wątku. Podczas tej instancji nazywany jest FN1 (). Kiedy jest nazywany, poprzedza ciąg: „Czy to, niech tak będzie!”Z„ Tak. ", mieć" Tak. Niech tak będzie!", który jest wyjściem dla całego programu wątków.

Funkcja main (), która jest wątkiem main (), instaluje wątek, thr1 z klasy wątku, z argumentami fn1 i globl1. Podczas tej instancji nazywany jest FN1 (). Funkcja, fn1 () to efektywny wątek dla obiektu, Thr1. Kiedy wywoływana jest funkcja, powinna działać od początku do końca.

THR1, który jest skutecznie fn1 (), instaluje wątek, thr2, z klasy wątku, z argumentami fn2 i globl2. Podczas tej instancji nazywany jest FN2 (). Funkcja, fn2 () to efektywny wątek dla obiektu, Thr2. Kiedy wywoływana jest funkcja, powinna działać od początku do końca.

Jeśli czytnik używa, kompilator G ++, może przetestować ten program wątków, dla kompilacji C ++ 20, z następującym poleceniem:

G ++ -Std = C ++ 2a Temp.cpp -lpthread -o temp

Autor to zrobił; uruchomił program i miał wyjście:

zakończenie wywoływane bez aktywnego wyjątku
Przerwane (zrzucone rdzeń)

Możliwe jest posiadanie takiego błędnego wyjścia, z właściwym wyjściem „Tak. Niech tak będzie!", rozkładane w środku. Jednak wszystko, co wciąż jest niedopuszczalne.

Problem z tym błędnym wyjściem polega na tym, że wątki nie zostały połączone. I tak wątki działały niezależnie, co prowadzi do zamieszania. Rozwiązaniem jest dołączenie do Thr1 do głównego wątku, a ponieważ Thr1 wywoła THR2, podobnie jak główny wątek wywołuje Thr1, Thr2 należy połączyć z Thr1. Jest to zilustrowane w następnej sekcji.

Dołączenie do wątku

Składnia do połączenia wątku do wątku wywołania to:

Threadobj.dołączyć();

gdzie łączenie () jest funkcją członkowską obiektu wątku. To wyrażenie musi znajdować się w korpusie nić wywołującej. To wyrażenie musi znajdować się wewnątrz ciała funkcji wywołania, który jest skutecznym wątkiem.

Poniższy program jest powtarzany powyższy program, ale z korpusem głównego wątku łączącego Thr1 i korpusem THR1 łączącym THR2:

#włączać
#włączać
#włączać
za pomocą przestrzeni nazw Std;
String GLOBL1 = String („SO,”);
String GLOBL2 = String ("czy to!");
void fn2 (string st2)
String GLOBL = GLOBL1 + ST2;
Cout << globl << endl;

void fn1 (string st1)
globl1 = "tak. „ + ST1;
Wątek THR2 (FN2, GLOBL2);
thr2.dołączyć();

int main ()

Wątek thr1 (FN1, GLOBL1);
thr1.dołączyć();
/ * niektóre stwierdzenia */
powrót 0;

Zwróć uwagę na pozycje oczekujące, w których instrukcje łączenia zostały włożone do programu. Wyjście to:

"Tak. Niech tak będzie!"

Bez cytatów, zgodnie z oczekiwaniami, czystymi i jasnymi, jednoznacznymi ”. THR2 nie potrzebuje żadnego oświadczenia o połączeniu w swoim ciele; nie nazywa żadnego wątku.

Korpus wywołania wątku łączy się z nić o nazwie.

przyszłość :: get ()

Biblioteka standardowa C ++ ma subbibrarę o nazwie Future. Ta subbibrary ma klasę o nazwie Future. Biblioteka ma również funkcję o nazwie async (). Klasa, przyszłość, ma funkcję członkowską o nazwie get (). Oprócz głównej roli, ta funkcja powoduje połączenie dwóch funkcji, które działają jednocześnie lub równolegle. Funkcje nie muszą być wątkami.

Funkcja async ()

Zauważ, że wątki przede wszystkim powracają. Wątek jest funkcją, która jest pod kontrolą. Niektóre funkcje nie zwracają void, ale zwracają coś. Więc niektóre wątki coś zwracają.

Funkcja async () może podjąć funkcję najwyższego poziomu jako argument i uruchomić funkcję jednocześnie lub równolegle z funkcją wywołania. W takim przypadku nie ma wątków, tylko funkcja wywołania i nazywana funkcja nazywana argumentem do funkcji async (). Prosta składnia funkcji asynchronicznej jest:

Future Fubj = Async (FN, FNARGS)

Funkcja asynchroniczna zwraca przyszły obiekt. Pierwszym argumentem tutaj, dla funkcji asynchronicznej, jest nazwa funkcji najwyższego poziomu. Po tym może być więcej niż jeden argument. Reszta argumentów to argumenty do funkcji najwyższego poziomu.

Jeśli funkcja najwyższego poziomu zwróci wartość, wartość ta będzie członkiem przyszłego obiektu. I jest to jeden ze sposobów naśladowania wątku, który zwraca wartość.

przyszłość :: get ()

Funkcja asynchroniczna zwraca przyszły obiekt. Ten przyszły obiekt ma wartość zwracaną funkcji, która jest argumentem funkcji asynchronicznej. Aby uzyskać tę wartość, należy użyć funkcji członka get () przyszłego obiektu. Scenariusz to:

Future Fubj = async (fn, fnargs);
Typ Futobj.Dostawać();

Kiedy funkcja get () jest wywoływana, korpus funkcji wywołania czeka (blokuje), aż funkcja asynchroniczna zwróci swoją wartość. Następnie reszta instrukcji poniżej instrukcji get () nadal wykonuje.

Poniższy program jest powyższy, w którym oficjalnie utworzono żaden wątek, a zamiast oświadczenia połączenia użyto oświadczenia get (). Do symulacji wątku zastosowano funkcję async (). Dwie funkcje najwyższego poziomu zostały zredukowane do jednej. Program to:

#włączać
#włączać
#włączać
za pomocą przestrzeni nazw Std;
String GLOBL1 = String („SO,”);
String GLOBL2 = String ("czy to!");
String fn (string st1, string st2)
String Conat = ST1 + ST2;
zwrócić się;

int main ()

Future FUT = async (FN, GLOBL1, GLOBL2);
String ret = fut.Dostawać(); // main () czeka tutaj
String Result = "Tak. " + ret;
Cout << result << endl;
powrót 0;

Zauważ, że przyszła biblioteka zamiast biblioteki wątków została uwzględniona. Wyjście to:

Tak. Niech tak będzie!

Wniosek

Gdy dotyczy jednego oświadczenia o połączeniu, zaangażowane są dwie funkcje najwyższego poziomu. Jeden to funkcja wywołania, a druga to funkcja wywołana. W ciele funkcji wywołującego znajduje się instrukcja łączenia. Te dwie funkcje mogą być zamknięte w wątku. Funkcja członkowska Join () nazywanego wątku znajduje się w korpusie wywołania. Po wywołaniu funkcji łączenia () wątek wywołujący czeka w tym momencie (blokuje) do momentu zakończenia wywołanego wątku; Zanim będzie działać.

Zastosowania wątków można uniknąć, używając funkcji async () w przyszłej bibliotece. Ta funkcja przyjmuje funkcję najwyższego poziomu jako argument i zwraca przyszły obiekt, który zawiera zwróconą wartość funkcji argumentu do funkcji async (). Aby uzyskać wartość zwracaną funkcji jako argument, należy użyć funkcji członka get (). Po wywołaniu funkcji członka get () korpus funkcji wywołania czeka w tym momencie (blokuje) do momentu zakończenia wywołanej funkcji; Zanim będzie działać.