Jak utworzyć wątek Linuksa w C
W systemie Linux można tworzyć wątki i zarządzać nimi w języku C/C++ przy użyciu biblioteki wątków POSIX (pthread). W przeciwieństwie do innych systemów operacyjnych, w Linuksie istnieje niewielka różnica między wątkiem a procesem. Dlatego Linux często odnosi się do swoich wątków jako procesów lekkich.
Korzystając z biblioteki pthread, możesz tworzyć wątki, czekać na ich zakończenie i jawnie je kończyć.
Historia użycia wątków w systemie Linux
Przed Linuksem w wersji 2.6 główną implementacją wątku był LinuxThreads. Ta implementacja miała znaczne ograniczenia pod względem wydajności i operacji synchronizacji. Limit maksymalnej liczby wątków, które mogą działać, ograniczył je do 1000.
W 2003 roku zespołowi kierowanemu przez programistów z IBM i RedHat udało się udostępnić projekt Native POSIX Thread Library (NPTL). Został po raz pierwszy wprowadzony w RedHat Enterprise w wersji 3, aby rozwiązać problemy z wydajnością wirtualnej maszyny Java w systemie Linux. Obecnie biblioteka GNU C zawiera implementacje obu mechanizmów wątkowania.
Żadne z nich nie jest implementacją zielonych wątków, którymi maszyna wirtualna zarządzałaby i działała w trybie wyłącznie użytkownika. Kiedy używasz biblioteki pthread, jądro tworzy wątek przy każdym uruchomieniu programu.
Możesz znaleźć informacje specyficzne dla wątku dla dowolnego uruchomionego procesu w plikach w /proc/<PID>/task . Jest to standardowa lokalizacja informacji o procesach w standardzie procfs Linux. W przypadku aplikacji jednowątkowych okaże się, że w tym katalogu istnieje rekord zadania o tej samej wartości co PID.
Logika pracy wątków
Wątki są jak procesy aktualnie uruchomione w systemie operacyjnym. W systemach jednoprocesorowych (np. mikrokontrolerach) jądro systemu operacyjnego symuluje wątki. Pozwala to na równoczesne uruchamianie transakcji poprzez krojenie.
Jednordzeniowy system operacyjny może tak naprawdę uruchamiać tylko jeden proces na raz. Jednak w systemach wielordzeniowych lub wieloprocesorowych procesy te mogą działać jednocześnie.
Tworzenie wątków w C
Możesz użyć funkcji pthread_create , aby utworzyć nowy wątek. Plik nagłówkowy pthread.h zawiera definicję swojej sygnatury wraz z innymi funkcjami związanymi z wątkami. Wątki używają tej samej przestrzeni adresowej i deskryptorów plików, co program główny.
Biblioteka pthread zawiera również niezbędną obsługę operacji mutex i warunkowych wymaganych do operacji synchronizacji.
Kiedy używasz funkcji biblioteki pthread, musisz upewnić się, że kompilator łączy bibliotekę pthread z twoim plikiem wykonywalnym. Jeśli to konieczne, możesz poinstruować kompilator, aby połączył się z biblioteką za pomocą opcji -l :
gcc -o test test_thread.c -lpthread
Funkcja pthread_create ma następującą sygnaturę:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
Zwraca 0, jeśli procedura zakończy się pomyślnie. Jeśli wystąpi problem, zwraca niezerowy kod błędu. W powyższym podpisie funkcji:
- Parametr wątku jest typu pthread_t . Utworzony wątek będzie zawsze dostępny z tym odnośnikiem.
- Parametr attr umożliwia określenie niestandardowego zachowania. Możesz użyć serii funkcji specyficznych dla wątku, zaczynając od pthread_attr_ , aby ustawić tę wartość. Możliwe dostosowania to zasady planowania, rozmiar stosu i zasady odłączania.
- start_routine określa funkcję, którą wykona wątek.
- arg reprezentuje ogólną strukturę danych przekazaną do funkcji przez wątek.
Oto przykładowa aplikacja:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *worker(void *data)
{
char *name = (char*)data;
for (int i = 0; i < 120; i++)
{
usleep(50000);
printf("Hi from thread name = %s\n", name);
}
printf("Thread %s done!\n", name);
return NULL;
}
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("Exiting from main program\n");
return 0;
}
Rodzaje gwintów
Kiedy wątek powraca z funkcji main() w aplikacji, wszystkie wątki kończą się, a system zwalnia wszystkie zasoby używane przez program. Podobnie, wychodząc z dowolnego wątku za pomocą polecenia takiego jak exit() , twój program zakończy wszystkie wątki.
Za pomocą funkcji pthread_join możesz zamiast tego czekać na zakończenie wątku. Wątek korzystający z tej funkcji będzie blokowany do momentu zakończenia oczekiwanego wątku. Zasoby, których używają z systemu, nie są zwracane nawet w przypadkach, takich jak zakończenie dołączanych wątków, nieplanowane przez procesor lub nawet niepowodzenie przyłączenia za pomocą ptread_join .
Czasami zdarzają się sytuacje, w których łączenie za pomocą pthread_join nie ma sensu; jeśli nie można na przykład przewidzieć, kiedy wątek się skończy. W takim przypadku można zapewnić, że system automatycznie zwróci wszystkie zasoby w punkcie, w którym wątek powraca.
Aby to osiągnąć, należy uruchamiać odpowiednie wątki ze statusem DETACHED . Podczas uruchamiania wątku stan DETACH można ustawić za pomocą wartości atrybutu wątku lub za pomocą funkcji pthread_detach :
int pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int pthread_detach(pthread_t thread);
Oto przykład użycia pthread_join(). Zamień główną funkcję w pierwszym programie na następującą:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("exiting from main program\n");
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}
Po skompilowaniu i uruchomieniu programu wynik będzie następujący:
Hi from thread Y
Hi from thread X
Hi from thread Y
...
Hi from thread Y
exiting from main program
Hi from thread X
...
Hi from thread X
Thread X done!
Hi from thread Y
Thread Y done!
Zakończenie wątku
Możesz anulować wątek za pomocą wywołania pthread_cancel, przekazując odpowiedni identyfikator pthread_t :
int pthread_cancel(pthread_t thread);
Możesz zobaczyć to w akcji w poniższym kodzie. Ponownie, tylko główna funkcja jest inna:
int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(1);
printf("====> Cancelling Thread Y!!\n");
pthread_cancel(th2);
usleep(100000);
printf("====> Cancelling Thread X!\n");
pthread_cancel(th1);
printf("exiting from main program\n");
return 0;
}
Dlaczego tworzone są wątki?
Systemy operacyjne zawsze próbują uruchamiać wątki na jednym lub większej liczbie procesorów, albo z utworzonej przez siebie listy, albo z listy wątków utworzonej przez użytkownika. Niektóre wątki nie mogą działać, ponieważ czekają na sygnał wejścia/wyjścia ze sprzętu. Mogą również czekać dobrowolnie, czekać na odpowiedź z innego wątku lub blokować ich inny wątek.
Możesz dostosować zasoby przydzielane do wątków tworzonych za pomocą pthread. Mogą to być niestandardowe zasady planowania lub w razie potrzeby można wybrać algorytmy planowania, takie jak FIFO lub Round-Robin.
Dodaj komentarz