Be Book Kernel Kit Kernel Kit Indeks

Pojęcia wątku i zespołu

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ątkowanie wątku

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.

Wątki i obrazy aplikacji

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.

Nakazanie wątkowi działania

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():

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().

Funkcja wątku

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.

Nazwy 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ół.

Priorytety wątku

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).

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.

Synchronizowanie wątków

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:

Sterowanie wątkiem

Są cztery sposoby sterowania wątkiem dopóki on działa:

Śmierć i wątek główny

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ą.

Przekazywanie danych do wątku

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.)


Be Book Kernel Kit Kernel Kit Indeks

Be Book,
...w ślicznym HTML...
dla BeOS wydanie 5

Copyright © 2000 Be, Inc. Wszelkie prawa zastrzeżone.