Jak utworzyć wątek Linuksa w C

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;
}

Wyjście z programu pokazujące dwa wątki działające jednocześnie

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;
}

Dane wyjściowe programu pokazujące uruchomione wątki, a następnie anulowane

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

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *