V části II (Vytvoření a identifikace vlákna) série Linux Thread jsme diskutovali o ID vláken, jak porovnat dvě ID vláken a jak vytvořit vlákno.
V tomto článku se zaměříme hlavně na to, jak je vlákno ukončeno.
Linux Threads Series:část 1, část 2, část 3 (tento článek).
Příkladový program C vlákna
Vezmeme-li stejný příklad, jaký je popsán v části II této série:
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); if(pthread_equal(id,tid[0])) { printf("\n First thread processing\n"); } else { printf("\n Second thread processing\n"); } for(i=0; i<(0xFFFFFFFF);i++); return NULL; } int main(void) { int i = 0; int err; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } sleep(5); return 0; }
Všimli jste si použití funkce „sleep()“? Dostali jste otázku, proč se používá funkce sleep()? Pokud ano, pak jste na správném místě, abyste dostali odpověď, a pokud ne, bude to také dobré čtení dopředu.
Pokud z výše uvedeného kódu odstraním funkci sleep() a poté se ji pokusím zkompilovat a spustit, zobrazí se mi následující výstup:
$ ./threads Thread created successfully First thread processing Thread created successfully
Ale pokud jej spustím s povolenou funkcí sleep(), pak výstup vidím jako :
$ ./threads Thread created successfully First thread processing Thread created successfully Second thread processing
Vidíme tedy, že chybí protokol ‚Zpracování druhého vlákna‘ v případě, že odstraníme funkci sleep().
Proč se to děje? Stalo se to proto, že těsně předtím, než se má naplánovat druhé vlákno, dokončilo rodičovské vlákno (ze kterého byla dvě vlákna vytvořena) své spuštění. To znamená, že výchozí vlákno, ve kterém byla spuštěna funkce main(), bylo dokončeno, a proto byl proces ukončen jako main() vráceno.
Ukončení vlákna
Jak již bylo uvedeno výše, každý program začíná alespoň jedním vláknem, což je vlákno, ve kterém se provádí funkce main(). Maximální životnost každého vlákna spuštěného v programu je tedy životnost hlavního vlákna. Pokud tedy chceme, aby hlavní vlákno čekalo, dokud nebudou dokončena všechna ostatní vlákna, pak existuje funkce pthread_join().
#include <pthread.h> int pthread_join(pthread_t thread, void **rval_ptr);
Výše uvedená funkce zajišťuje, že její nadřazené vlákno se neukončí, dokud není hotovo. Tato funkce je volána z nadřazeného vlákna a první argument je ID vlákna, na které se má čekat, a druhý argument je návratová hodnota vlákna, na které chceme, aby nadřazené vlákno čekalo. Pokud nás návratová hodnota nezajímá, můžeme tento ukazatel nastavit na hodnotu NULL.
Pokud klasifikujeme na širší úrovni, pak vidíme, že vlákno může skončit třemi způsoby:
- Pokud se vlákno vrátí ze své úvodní rutiny.
- Pokud je zrušeno jiným vláknem. Zde použitá funkce je pthread_cancel().
- Pokud ze sebe sama zavolá funkci pthread_exit().
Zaměření by zde bylo na pthread_exit(). Jeho prototyp je následující:
#include <pthread.h> void pthread_exit(void *rval_ptr);
Vidíme tedy, že tato funkce přijímá pouze jeden argument, což je návrat z vlákna, které tuto funkci volá. K této vrácené hodnotě přistupuje nadřazené vlákno, které čeká na ukončení tohoto vlákna. Návratová hodnota vlákna ukončeného funkcí pthread_exit() je dostupná ve druhém argumentu pthread_join, který byl právě vysvětlen výše.
Příklad ukončení vlákna C
Vezměme si příklad, kde použijeme výše diskutované funkce:
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; int ret1,ret2; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); for(i=0; i<(0xFFFFFFFF);i++); if(pthread_equal(id,tid[0])) { printf("\n First thread processing done\n"); ret1 = 100; pthread_exit(&ret1); } else { printf("\n Second thread processing done\n"); ret2 = 200; pthread_exit(&ret2); } return NULL; } int main(void) { int i = 0; int err; int *ptr[2]; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } pthread_join(tid[0], (void**)&(ptr[0])); pthread_join(tid[1], (void**)&(ptr[1])); printf("\n return value from first thread is [%d]\n", *ptr[0]); printf("\n return value from second thread is [%d]\n", *ptr[1]); return 0; }
Ve výše uvedeném kódu:
- Vytvořili jsme dvě vlákna pomocí pthread_create()
- Funkce start pro obě vlákna je stejná, tj. doSomeThing()
- Vlákna ukončí funkci start pomocí funkce pthread_exit() s návratovou hodnotou.
- V hlavní funkci po vytvoření vláken jsou volány funkce pthread_join(), aby počkaly na dokončení dvou vláken.
- Jakmile jsou obě vlákna dokončena, k jejich návratové hodnotě přistupuje druhý argument ve volání pthread_join().
Výstup výše uvedeného kódu vyjde jako :
$ ./threads Thread created successfully Thread created successfully First thread processing done Second thread processing done return value from first thread is [100] return value from second thread is [200]
Vidíme tedy, že obě vlákna se kompletně provedou a jejich návratová hodnota je zpřístupněna ve funkci main.