Takie „zaspanie”
w'tku rzadko ma bezpo$redni wp!yw na wynik operacji wykonywanych przez program,
ale ju" w przypadku szybkiej gry mo"e powodowa% pomini(cie niektórych klatek animacji, a w przypadku aplikacji czasu rzeczywistego mo"e oznacza% pomini(cie
przydzia!u czasu procesora.
Trzecim, najlepszym rozwi'zaniem jest u"ycie gotowych elementów biblioteki stan-
dardowej j(zyka C++ umo"liwiaj'cych oczekiwanie na okre$lone zdarzenie. Najprost-
szym mechanizmem oczekiwania na zdarzenie generowane przez inny w'tek (na przy-
k!ad zdarzenie polegaj'ce na umieszczeniu dodatkowego zadania w potoku) jest tzw.
zmienna warunkowa. Zmienna warunkowa jest powi'zana z pewnym zdarzeniem lub
warunkiem oraz co najmniej jednym w'tkiem, który czeka na spe!nienie tego warunku.
W'tek, który odkrywa, "e warunek jest spe!niony, mo"e powiadomi& pozosta!e w'tki oczekuj'ce na t( zmienn' warunkow', aby je obudzi% i umo"liwi% im dalsze przetwarzanie.
4.1.1.
Oczekiwanie na spe)nienie warunku
za pomoc" zmiennych warunkowych
Biblioteka standardowa j(zyka C++ udost(pnia dwie implementacje mechanizmu
zmiennych warunkowych w formie klas std::condition_variable i std::condition_
variable_any. Obie klasy zosta!y zadeklarowane w pliku nag!ówkowym <condition_
variable>. W obu przypadkach zapewnienie w!a$ciwej synchronizacji wymaga u"y-
cia muteksu — pierwsza klasa jest przystosowana tylko do obs!ugi muteksów typu
std::mutex, natomiast druga klasa obs!uguje wszystkie rodzaje muteksów spe!niaj'cych
pewien minimalny zbiór kryteriów (st'd przyrostek _any). Poniewa" klasa std::condition_
variable_any jest bardziej uniwersalna, z jej stosowaniem wi'"' si( dodatkowe
koszty w wymiarze wielko$ci, wydajno$ci i zasobów systemu operacyjnego. Je$li wi(c
nie potrzebujemy dodatkowej elastyczno$ci, powinni$my stosowa% klas( std::condition_
variable.
Jak nale"a!oby u"y% klasy std::condition_variable do obs!ugi przyk!adu opisanego
na pocz'tku tego podrozdzia!u — jak sprawi%, "e w'tek oczekuj'cy na wykonanie jakie-
go$ zadania b(dzie u$piony do momentu, w którym b(d' dost(pne dane do przetwo-
rzenia? Na listingu 4.1 pokazano przyk!ad kodu implementuj'cego odpowiednie roz-
wi'zanie przy u"yciu zmiennej warunkowej.
Listing 4.1. Oczekiwanie na dane do przetworzenia za pomoc= klasy
std::condition_variable
std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
void data_preparation_thread()
{
while(more_data_to_prepare())
{
data_chunk const data=prepare_data();
Kup ksiąĪkĊ
Poleü ksiąĪkĊ
96
ROZDZIA 4. Synchronizacja wspó bie%nych operacji
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);
data_cond.notify_one();
}
}
void data_processing_thread()
{
while(true)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(
lk,[]{return !data_queue.empty();});
data_chunk data=data_queue.front();
data_queue.pop();
lk.unlock();
process(data);
if(is_last_chunk(data))
break;
}
}
Na pocz'tku kodu zdefiniowano kolejk( , która b(dzie u"ywana do przekazywania
danych pomi(dzy dwoma w'tkami. Kiedy dane s' gotowe do przetworzenia, w'tek,
który je przygotowa!, blokuje muteks chroni'cy kolejk( za pomoc' klasy std::lock_
guard i umieszcza nowe dane w kolejce . W'tek wywo!uje nast(pnie funkcj( sk!a-
dow' notify_one() dla obiektu klasy std::condition_variable, aby powiadomi% ocze-
kuj'cy w'tek (je$li taki istnieje) o dost(pno$ci nowych danych .
W tym modelu drug' stron' komunikacji jest w'tek przetwarzaj'cy te dane. W'tek
przetwarzaj'cy najpierw blokuje muteks, jednak tym razem u"yto do tego celu klasy
std::unique_lock zamiast klasy std::lock_guard
— przyczyny tej decyzji zostan'
wyja$nione za chwil(. W'tek wywo!uje nast(pnie funkcj( wait() dla obiektu klasy
std::condition_variable. Na wej$ciu tego wywo!ania w'tek przekazuje obiekt blokady
i funkcj( lambda reprezentuj'c' warunek, który musi zosta% spe!niony przed przyst'-
pieniem do dalszego przetwarzania . Funkcje lambda to stosunkowo nowy element
(wprowadzony w standardzie C++11), który umo"liwia pisanie funkcji anonimowych
w ramach innych wyra"e&. Wspomniane rozwi'zanie wprost idealnie nadaje si( do
wskazywania predykatów w wywo!aniach takich funkcji biblioteki standardowej jak
wait(). W tym przypadku prosta funkcja lambda []{return !data_queue.empty();}
|