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ę stworzyć Natywna biblioteka wątków POSIX (NPTL) dostępny projekt. 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 dotyczące konkretnego wątku dla dowolnego uruchomionego procesu w plikach poniżej /proc/
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 systemy wielordzeniowe lub wieloprocesorowe, procesy te mogą przebiegać jednocześnie.
Tworzenie wątków w C
Możesz użyć pthread_create funkcja tworzenia nowego wątku. The pthread.h plik nagłówkowy zawiera definicję 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.
Gdy korzystasz z funkcji biblioteki pthread, musisz upewnić się, że kompilator łączy pliki wątek bibliotekę do pliku wykonywalnego. W razie potrzeby możesz poinstruować kompilator, aby łączył się z biblioteką za pomocą -l opcja:
gcc -o test test_wątek.c -lpwątek
Funkcja pthread_create ma następującą sygnaturę:
intpthread_create(pthread_t *nitka, konstpthread_attr_t *atrakcja, próżnia *(*start_rutyna)(próżnia *), próżnia * argument)
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:
- The nitka parametr jest typu pthread_t. Utworzony wątek będzie zawsze dostępny z tym odnośnikiem.
- The atr parametr pozwala określić niestandardowe zachowanie. Możesz użyć szeregu 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_rutyna określa funkcję, którą wątek będzie uruchamiał.
- arg reprezentuje ogólną strukturę danych przekazaną do funkcji przez wątek.
Oto przykładowa aplikacja:
#włączać
#włączać
#włączać
#włączaćpróżnia *pracownik(próżnia *dane)
{
zwęglać * imię = (zwęglać*)dane;
Do (int ja = 0; ja < 120; i++)
{
śpisz(50000);
drukujf(„Cześć z wątku nazwa = %s\n”, nazwa);
}
drukujf("Wątek %s wykonany!\n", nazwa);
powrótZERO;
}
intgłówny(próżnia)
{
pthread_t th1, th2;
pthread_create(&th1, ZERO, pracownik, „X”);
pthread_create(&th2, ZERO, pracownik, „Y”);
spać(5);
drukujf("Wyjście z programu głównego\n");
powrót0;
}
Rodzaje gwintów
Kiedy wątek powraca z główny() funkcji 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 an Wyjście(), twój program zakończy wszystkie wątki.
z pthread_join zamiast tego możesz poczekać 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 połączenia z 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 rozpocząć odpowiednie wątki z WOLNOSTOJĄCY status. Podczas zakładania wątku ODŁĄCZYĆ status można ustawić za pomocą wartości atrybutu wątku lub za pomocą pthread_detach funkcjonować:
intpthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
intpthread_detach(pthread_t nitka);
Oto przykład użycia funkcji pthread_join(). Zamień główną funkcję w pierwszym programie na następującą:
intgłówny(próżnia)
{
pthread_t th1, th2;
pthread_create(&th1, ZERO, pracownik, „X”);
pthread_create(&th2, ZERO, pracownik, „Y”);
spać(5);
drukujf("wyjście z programu głównego\n");
pthread_join (th1, ZERO);
pthread_join (th2, ZERO);
powrót0;
}
Po skompilowaniu i uruchomieniu programu wynik będzie następujący:
Cześć z wątku Y
Witam z wątku X
Cześć z wątku Y
...
Cześć z wątku Y
wyjście z programu głównego
Witam z wątku X
...
Witam z wątku X
Wątek X zakończony!
Cześć z wątku Y
Wątek Y zakończony!
Zakończenie wątku
Możesz anulować wątek za pomocą wywołania pthread_cancel, przekazując odpowiedni pthread_t ID:
intpthread_cancel(pthread_t nitka);
Możesz zobaczyć to w akcji w poniższym kodzie. Znowu tylko tzw główny funkcja jest inna:
intgłówny(próżnia)
{
pthread_t th1, th2;
pthread_create(&th1, ZERO, pracownik, „X”);
pthread_create(&th2, ZERO, pracownik, „Y”);
spać(1);
drukujf("> Anulowanie wątku Y!!\n");
pthread_cancel (th2);
śpisz(100000);
drukujf("> Anulowanie wątku X!\n");
pthread_cancel (th1);
drukujf("wyjście z programu głównego\n");
powrót0;
}
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.