Ludzie pragną czasami się rozstawać, żeby móc tęsknić, czekać i cieszyć się z powrotem.
Jak wida%,
tak"e funkcja wait_and_pop() uwzgl(dnia stan zmiennej warunkowej .
Napisanie drugiej wersji przeci'"onej funkcji wait_and_pop() nie stanowi "adnego
problemu; tak"e pozosta!e funkcje mo"na niemal skopiowa% z przyk!adu stosu pokaza-
nego na listingu 3.5. Ostateczn' wersj( implementacji kolejki pokazano na listingu 4.5.
Listing 4.5. Kompletna definicja klasy kolejki gwarantuj=cej bezpieczeUstwo
przetwarzania wielow=tkowego (dziQki uJyciu zmiennych warunkowych)
#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
template<typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
Muteks musi byL modyfikowalny
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue()
{}
Kup ksiąĪkĊ
Poleü ksiąĪkĊ
4.1.
Oczekiwanie na zdarzenie lub inny warunek
101
threadsafe_queue(threadsafe_queue const& other)
{
std::lock_guard<std::mutex> lk(other.mut);
data_queue=other.data_queue;
}
void push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}
void wait_and_pop(T& value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{return !data_queue.empty();});
value=data_queue.front();
data_queue.pop();
}
std::shared_ptr<T> wait_and_pop()
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{return !data_queue.empty();});
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool try_pop(T& value)
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return false;
value=data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return std::shared_ptr<T>();
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
Kup ksiąĪkĊ
Poleü ksiąĪkĊ
102
ROZDZIA 4. Synchronizacja wspó bie%nych operacji
Mimo "e empty() jest sta!' funkcj' sk!adow' i mimo "e parametr other konstruktora
kopiuj'cego jest sta!' referencj', pozosta!e w'tki mog' dysponowa% niesta!ymi refe-
rencjami do tego obiektu i wywo!ywa% funkcje sk!adowe zmieniaj'ce jego stan, zatem
blokowanie muteksu wci'" jest konieczne. Poniewa" blokowanie muteksu jest operacj'
zmieniaj'c' stan obiektu, obiekt muteksu nale"y oznaczy% jako modyfikowalny (ang.
mutable) , tak aby mo"na by!o blokowa% ten muteks w ciele funkcji empty() i kon-
struktora kopiuj'cego.
Zmienne warunkowe s' przydatne tak"e w sytuacji, w której wiele w'tków czeka na
to samo zdarzenie. Je$li celem stosowania w'tków jest dzielenie obci'"enia i je$li tylko
jeden w'tek powinien reagowa% na powiadomienie, mo"na zastosowa% dok!adnie tak'
sam' struktur( jak ta z listingu 4.1 — wystarczy uruchomi% wiele instancji w'tku prze-
twarzaj'cego dane. Po przygotowaniu nowych danych wywo!anie funkcji notify_one()
spowoduje, "e jeden z w'tków aktualnie wykonuj'cych funkcj( wait() sprawdzi waru-
nek. Poniewa" do struktury data_queue w!a$nie dodano nowe dane, funkcja wait()
zwróci sterowanie. Nie wiadomo, do którego w'tku trafi powiadomienie ani nawet czy
istnieje w'tek oczekuj'cy na to powiadomienie (nie mo"na przecie" wykluczy%, "e
wszystkie w'tki w danej chwili przetwarzaj' swoje dane).
Warto te" pami(ta% o mo"liwo$ci oczekiwania na to samo zdarzenie przez wiele
w'tków, z których ka"dy musi zareagowa% na powiadomienie. Opisany scenariusz mo"e
mie% zwi'zek z inicjalizacj' wspó!dzielonych danych, gdzie wszystkie w'tki przetwa-
rzaj'ce operuj' na tych samych danych i musz' czeka% albo na ich inicjalizacj( (w takim
przypadku istniej' lepsze mechanizmy — patrz punkt 3.3.1 w rozdziale 3.), albo na ich
aktualizacj( (na przyk!ad w ramach okresowej, wielokrotnej inicjalizacji). W opisanych
przypadkach w'tek przygotowuj'cy dane mo"e wywo!a% funkcj( sk!adow' notify_all()
dla zmiennej warunkowej (zamiast funkcji notify_one()). Jak nietrudno si( domy$li%,
funkcja powoduje, "e wszystkie w'tki aktualnie wykonuj'ce funkcj( wait() sprawdz'
warunek, na który czekaj'.
Je$li w'tek wywo!uj'cy w za!o"eniu ma oczekiwa% na dane zdarzenie tylko raz, czyli