|
Wątek jest synchronicznym procesem (wyjaśnienie tłum.), który wykonuje ciąg instrukcji programu. Zespół jest grupą wątków, które tworzą pjedynczy program lub aplikację.Każda aplikacja ma co najmniej jeden wątek: kiedy odpalisz aplikację, wątek poczatkowy - główny wątek - jest automatycznie tworzony (lub zapoczątkowany) i odpowiada, że działa. Główny wątek wykonuje wszechobecną funkcję main(), przechodzi poprzez funkcje, które są wywoływane z main() i jest automatycznie usuwany (lub zabijany) gdy funkcja main() się zakończy.
System operacyjny Be jest wielowątkowy: z głównego wątku możesz zapoczątkować i uruchomić dodatkowe wątki; z każdego z tych wątków możesz zapoczątkować i uruchomić dalsze wątki i tak dalej. Wszystkie wątki we wszystkich aplikacjach działają jednocześnie i asynchronicznie jeden względem drugiego.
Wątki są niezależne jeden od drugiego. Bardziej szczegółowo, dany wątek nie jest właścicicielem innych wątków, które on zapoczątkował. Przykładowo, jeśli wątek A zapoczątkował wątek B a wątek a ginie (z jakiegokolwiek powodu), wątek B będzie kontynuował działanie. (Ale zanim zdobędziesz się na pomysł "wzajemnego przeskakiwania" wątków, powinieneś wziąć pod uwagę to "Śmierć i główny wątek".)
![]()
Wątki i funkcja fork() z POSIX'a nie są zgodne. Nie możesz mieszać wywołań funkcji spawn_thread() (ta funkcja, która tworzy nowy wątek) i funkcji fork() w tej samej aplikacji: Jeśli wywołujesz spawn_thread() a potem próbujesz wywołać fork(), wywołanie fork() będzie zawodzić. I na odwrót. Chociaż wątki są niezależne, są umieszczone w grupach nazywanych zespołami. Zespół składa się z głównego wątku i wszystkich pozostałych wątków, które "pochodzą" od niego (które są zapoczątkowane bezpośrednio przez główny wątek lub przez wątek, który był zapoczątkowany przez wątek główny i tak dalej). Widoczny z wyższego poziomu zespół jest grupą wątków, które są utworzone przez pojedynczą aplikację. Nie możesz "przekazać" wątków z jednego zespołu do innego. Zespół jest ustanowiony wtedy, kiedy jest zapoczątkowany wątek; pozostaje on tym samym przez całe życie wątku.
Wszystkie wątki w pojedynczym zespole współdzielą tę samą przestrzeń adresową: zmienne globalne, które są zadeklarowane przez jeden wątek będą widoczne dla wszystkich innych wątków w tym zespole.
Zapoczątkowujesz wątek przez wtwołanie funkcji spawn_thread(). Ta funkcja przypisuje i zwraca widoczną w systemie liczbę thread_id, którą Ty używasz do identyfikowania nowego wątku w późniejszych wywołaniach funkcji. Prawidłowymi wartościami thread_id są liczby całkowite dodatnie; możesz sprawdzić czy zapoczątowanie przebiegło z powodzeniem, w taki sposób:
thread_id my_thread = spawn_thread(...);
if ((my_thread) < B_OK)
/* niepowodzenie */
else
/* powodzenie */
Argumenty dla spawn_thread(), które są sprawdzane poprzez ten opis, dostarczają informacje takie jak to co wątek przypuszczalnie będzie robił, pilność jego działania i inne.
Pojęcie otoczenia zapoczątkowującego wątek jest czynnością ładowania kodu wykonywalnego (lub ładowanie obrazu aplikacji). Jest to wykonywane przez wywołanie funkcji load_image(). Załadowanie obrazu powoduje oddzielny program, identyfikowany jako plik, do odpalenia przez system. Aby uzyskać więcej informacji o funkcji load_image() zobacz do rozdziału Obrazy.
Zapoczątkowanie wątku nie jest wystarczające, aby spowodować jego działanie. Aby nakazać wątkowi rozpoczęcie działania, musisz przekazać jego numer identyfikacyjny thread_id do number jedenej z funkcji: resume_thread() lub wait_for_thread():
- resume_thread() rozpoczyna działanie nowgo wątku i natychmiast zwraca wartość. Nowy wątek działa jednocześnie i asynchronicznie z wątkiem z którego była wywołana funkcja resume_thread().
- wait_for_thread() rozpoczyna działanie wątku ale nie powraca dopóki wątek nie zakończy działania. (Możesz również wywołać wait_for_thread() na wątku który już działa.)
Z tych dwóch funkcji, resume_thread() jest bardziej powszechnym sposobem uruchamiania wątku, który był utworzony poprzez funkcję spawn_thread(). wait_for_thread() jest zwykle używana do uruchamiania wątku, który był utworzony poprzez load_image().
Kiedy wywołujesz spawn_thread(), musisz zidentyfikować funkcję wątku nowego wątku. Jest ot globalna funkcja C (lub statyczna funkcja członkowska C++), która będzie wykonywać nowy wątek, gdy nakażesz mu działać. Funkcja wątku, zdefiniowana jako thread_func, pobiera pojedynczy argument (void *) i zwraca kod błędu typu int32. Kiedy funkcja wątku się zakończy, wątek jest automatycznie likwidowany.Przekazujesz funkcję wątku jako pierwszy argument do spawn_thread(). Na przykład, zapoczątkowujemy tutaj wątek, który używa funkcji nazywanej lister() jako jego funkcji wątku. Pierwszy argument do funkcji spawn_thread() jest przekazywany do funkcji wątku:
int32 lister(void *data)
{
/* Rzutuj argument. */
BList *listObj = (BList *)data;
...
}
int32 main()
{
BList *listObj = new BList();
thread_id my_thread;
my_thread = spawn_thread(lister, ..., (void *)listObj);
resume_thread(my_thread);
...
}
Zobacz do Przekazywanie danych do wątku dla innych metod przekazywania danych do wątku.
Wątek może być dany pod nazwą, którą przypisujesz poprzez drugi argument do spawn_thread(). Nazwa może mieć długość 32 znaków (takie jak reprezentowane przez stałą B_OS_NAME_LENGTH) i nie musi być unikalna - więcej niż jeden wątek może mieć tą samą nazwę.Możesz szukać wątku opartego na jego nazwie przez przekazanie nazwy do funkcji find_thread(); funkcja zwraca thread_id tak nazwanego wątku. Jeśli dwa lub więcej wątków nosi to samo imię, funkcja find_thread() zwraca pierwszy z tych wątków, który znalazła.
Możesz uzyskać thread_id wywoływanego wątku przez przekazanie NULL do funkcji find_thread():
thread_id this_thread = find_thread(NULL); Aby uzyskać nazwę wątku, musisz zaglądnąć do struktury thread_info wątku. Ta struktura jest opisana w opisie funkcji get_thread_info().
Jesteś niezadowowlony z nazwy wątku? Użyj funkcji rename_thread() aby zmienić nazwę. Zrób głupka z twoich przyjaciół.
W wielowątkowym środowisku, procesor musi dzielić swoją uwagę pomiędzy kandydującymi wątkami, wykonując po kilka instrukcji z tego wątku, potem kilka z tamtego wątku i tak dalej. Ale dzielenie uwagi nie zawsze jest równe: możesz przypisać wyższy lub niższy priorytet do wątku i tak zadeklarować go aby był mniej lub bardziej ważny niż inne wątki.Przypisujesz priorytet wątku (całkowity) jako trzeci argument funkcji spawn_thread(). Są dwie kategorie priorytetów: "podziału czasu" (ang. time-sharing) i "czasu rzeczywistego" (ang. real-time).
- Z podziałem czasu (wartości od 1 do 99). Wątek z podziałem czasu jest wykonywany tylko, jeśli nie ma żadnych wątków czasu rzeczywistego w kolejce gotowych wątków. W przypadku braku wątków czasu rzeczywistego, wybierany jest wątek z podziałem czasu, do uruchomienia raz na każdy "kwant planisty" (aktualnie, każde trzy milisekundy). Wyższa wartość priorytetu wątku z podziałem czasu, większa szansa, że to on będzie nastęnym wątkiem do ruchomienia.
- Czasu rzeczywistego (100 i większe). Wątek czasu rzeczywistego jest wykonywany jak tylko jest gotowy. Jeśli więcej niż jeden wątek jest gotowy w tym samym czasie, wykonywany jest najpierw wątek z najwyższym priorytetem. Wątek jest przeznaczony do uruchomienia bez bycia wywłaszczonym (wyjątkowo przez wątek czasu rzeczywistego z wyższym priorytetem) dopóki jest on blokowany, uśpiony, jest wstrzymany lub w przeciwnym wypadku rezygnuje ze swojego żądania o uwagę.
Kernel Kit definiuje siedem stałych wartości priorytetu (patrz do listy Wartości priorytetu wątku w rozdziale Threads). Chociaż możesz użyć inych, wartości "pośrednich" jako argument priorytetu w funkcji spawn_thread(), to sugerowane jest, żebyś trzymał się tych.
Ponadto możesz wywołać funkcję suggest_thread_priority() aby pozwolić zestawowi Kernel Kit określić dobry priorytet dla Twojego wątku. Ta funkcja pobiera informacje o szeregowanym wątku i poprzebuje sensowną wartość priorytetu do użycia gdy zapoczątkowuje wątek.
Jeat czasem tak, że chciałbyś by pewien wątek zatrzymał się w określonym punkcie aż jakiś inny (znany) wątek zakończy swoje zadanie. Są trzy sposoby wykonania tego rodzaju synchronizacji:
- Bardziej ogólnymi środkami do synchroznizowania wątków jest użycie semafora. Mechanizm semafora jest opisany bardziej szczegółówo w rozdziale Semafory.
- Synchronizacja jest czasami efektem ubocznym wysyłania danych między wątkami. Jest to wyjaśnione w rozdziale "Przekazywanie danych do wątku" i w rozdziale Porty.
- W końcu możesz nakazać wątkowi, aby zaczekał na jakiś inny ginący wątek prze wywołanie funkcji wait_for_thread(), jak opisano wcześniej.
Są cztery sposoby sterowania wątkiem dopóki on działa:
- Możesz uśpić wywoływany wątek na jakąś liczbę mikrosekund poprzez funkcje snooze() i snooze_until().
- Możesz zawiesić wykonanie jakiegoś wątku przez funkcję suspend_thread(). Wątek pozostaje zawieszony, aż go "odwiesisz" przez wywołanie funkcji resume_thread() lub wait_for_thread().
- Możesz wysłać "sygnał" POSIX wątkowi przez funkcję send_signal(). Sygnał SIGCONT próbuje odblokować zablokowany albo uśpiony wątek bez zabijania go; wszystkie inne sygnały zabijają wątek. Aby przedefiniować (zmienić - przyp. tłum.) to zachowanie, możesz zainstalować swoje własne procedury obsługi sygnału.
- Możesz zabić wywoływany wątek przez funkcję exit_thread(), albo zabić jakiś inny wątek przez funkcję kill_thread(). Uczucie swędzenia? Spróbuj zabić cały zespół wątków: funkcja kill_team() jest więcej niż wywołaniem systemowym. To jest terapia.
Jak wspomniano wcześniej, to sterowanie, które jest narzucone na konkretny wątek nie powoduje przeprowadzania inspekcji na "dzieciach", które zostały zapoczątkowane z tego wątku. Jednakże, śmierć głównego wątku aplikacji może oddziaływać na inne wątki:
![]()
Kiedy główny wątek umiera, gra jest w zasadzie skończona. Główny wątek utrzymuje stertę zespołu, jego statycznie przydzielone obiekty i inne zasoby zespołowe - takie jak dostęp do standardowego wejścia-wyjścia - wewnątrz niego. Może to poważnie okaleczyć jakieś wątki, które pozostają po śmierci głównego wątku. Jest możliwe utworzenie aplikacji w której główny wątek ustanawia jeden lub więcej wątków, pozostawia je działajace i potem ginie. Ale takie aplikacje powinny być rzadkością. Ogółnie powinienieś próbować utrzymywać główny wątek dopóki wszystkie inne wątki w zespole nie zginą.
Każdy wątek ma pamięć podręczną komunikatu. Możesz zapisać do pamięci podręcznej komunikatu w wątku przez funkcję send_data() . Wątek może pobrać Twój komunikat (połączenie liczby całkowitej i buforu) przez funkcję receive_data(). Pamięć podręczna jest głęboka tylko na jeden komunikat; jeśli w pamięci podręcznej będzie już komunikat, funkcja send_data będzie blokowana. I odwrotnie, jeśli nie ma żadnego komunikatu w pamięci podręcznej, blokowana będzie funkcja receive_data().Możesz też przekazać dane do wątku przez port. Dowolnie głębokie, porty są bardziej elastyczne niż pamięć podręczna komunikatu. Aby zobaczyć szczegóły popatrz do Porty.
1) Jest to uproszczenie. W rzeczywistości procesy i wątki to różne twory. Proces może się składać z wielu wątków. (przyp. tłum.)
|