dla programistów
Programowanie równoległe z Qt
Programowanie
równoległe z Qt
Piotr Pszczółkowski
Bardzo często, w programach wymagających dużych szybkości przetwarzania, musimy posiłkować
się mechanizmami przetwarzania równoległego. Można to porównać do człowieka, który robi wiele
rzeczy na raz (np. czyta książkę, jednocześnie słucha muzyki i głaszcze swojego ulubionego kota).
Oczywiście mógłby to wszystko zrobić osobno, skupiając się tylko na jednej, właśnie wykonywanej,
czynności. Ale zajęłoby to wielokrotnie więcej czasu. Dokładnie tak samo jest z programami
komputerowymi.
odstawowymi pojęciami rozumia- W dalszej części artykułu interesują nas już wego wkładu pracy programisty tworzącego
nymi przez komputer są procesy tylko wątki. Z tej przyczyny, że uruchamia- aplikację. Wszystkie wątki są w tej samej prze-
(ang. process) i wątki (ang. thre- nie procesu poprzez exec, lub sklonowanie strzeni adresowej (przestrzeni adresowej pro-
Pad). Wątek to ciąg poleceń maszy- poprzez fork, jest dla systemu operacją skom- cesu, który je uruchomił). Mają dostęp do
nowych wykonywanych przez procesor kom- plikowana, przez co i czasochłonną. wszystkich zasobów (np. zmiennych) procesu.
putera, mających na celu wykonanie określone- Jedyne co ma na własność każdy wątek, to jest
go zadania. Relacje zachodzące pomiędzy pro- Wątki czy to się opłaca? zawartość stosu (czyli np. zmienne lokalne w
cesem i wątkami można opisać używając analo- W większości przypadków - tak. Można by po- funkcji wątku) i zawartość rejestrów proceso-
gii. Można powiedzieć, że proces to dom (zwy- wiedzieć jak najbardziej . W przypadku kom- ra. Problemem jest to, że mogą chcieć używać
kły mieszkalny dom) a wątki to mieszkańcy te- puterów wieloprocesorowych lub kompute- wspólnych zasobów JEDNOCZEŚNIE. A na
goż domu. Dom to taki pojemnik o określonych rów z procesorem HT (Hyper Threading), czy to niestety, nie możemy pozwolić. Pozwolenie
atrybutach (adres, liczba pokoi, powierzchnia wielordzeniowych (MultiCore) odpowiedz jest na jednoczesny dostęp, to najprostsza droga do
mieszkalna itd.), czyli takie wydzielone środo- oczywiście pozytywna. Natomiast w przypad- katastrofy. Podstawowa zasada: kilka wątków
wisko dla jego mieszkańców. Dom (jak i pro- ku innego, zwykłego procesora, może być nie może JEDNOCZEŚNIE mieć dostępu do te-
ces) tak naprawdę nic nie robi, jest to obiekt różnie. Jeżeli istnieje tylko jeden potok przetwa- go samego zasobu.
pasywny. Aktywni są natomiast jego miesz- rzania w procesorze, to co nam po tym że uży-
kańcy, oglądają TV, odrabiają lekcje, piszą ar- wamy wątków? Przecież czas pracy procesora Mutex sposób
tykuły dla gazet itd. Tak też jest i z wątkami, zużyty przez jeden z wątków, zostanie odebra- na współdzielenie zasobów
to one wykonują kod maszynowy, wykonu- ny innemu. Sumarycznie program powinien Aby uniknąć jednoczesnego dostępu do tego
ją nakreślone przez programistę zadania. Pro- trwać tyle samo. Owszem. Byłoby tak gdyby samego zasobu przez kilka wątków, wymyślo-
ces jest środowiskiem, w którym pracują wąt- każdy z wątków wykonywał swoją pracę nie- no coś co się nazywa mutex. Nazwa pochodzi
ki. Jedna uruchomiona przez nas aplikacja to je- przerwanie. Ale w normalnym programie użyt- z połączenia części dwóch słów: mut jak mu-
den proces. Ta sama aplikacja uruchomiona po- kowym tak nie jest. Wątek (-ki) odpowiedzial- tual (pol. wzajemny, wspólny, obopólny) i ex
nownie, lub sklonowana poprzez użycie pole- ny za komunikację z użytkownikiem, z punktu jak exclusion (pol. wyłączenie, wykluczenie,
cenia fork, to kolejny proces. Każdy proces ma widzenia procesora, jest prawie ciągle w sta- usunięcie). Czyli mówiąc po polsku, muteks
co najmniej jeden wątek. Ale (i to jest to, co nas nie oczekiwania na reakcje użytkownika. W tym WYKLUCZA WSPÓLNY dostęp do chronio-
interesuje) jeden proces może uruchomić wie- czasie inny watek (-ki) przetwarzający może nego przez niego zasobu (np. zespołu zmien-
le wątków, z których każdy będzie zajmować zrobić coś naprawdę pożytecznego. I to w spo- nych). Jest to jakby wartownik który pilnuje
się swoją własną pracą. Wyniki tych prac oczy- sób niezauważalny przez użytkownika (tzn. aby dany zasób obsługiwał w danej chwili tyl-
wiście muszą(/mogą) być udostępnione pozo- użytkownik albo by nie zaobserwował ob- ko i wyłącznie jeden wątek.
stałym elementom składowym procesu. Możli- ciążenia sytemu, lub byłoby ono nieznaczne).
wa jest także bardzo ścisła kooperacja pomię- Podsumowując: w normalnej aplikacji użytko- QThread i QMutex
dzy wątkami. wej użycie wątków jest prawie zawsze sen- dla programujących w Qt
Zasada ogólna: sowne. Biblioteka Qt zawiera klasy QThread i QMu-
tex pozwalające programiście w sposób nieza-
" Każdy proces to co najmniej jeden wą- Problemy też są leżny od platformy, na proste i szybkie tworze-
tek. Oprócz oczywistych zalet stosowania wątków nie aplikacji z użyciem wątków. Innymi bar-
" Wątek należy tylko i wyłącznie do jedne- (przyspieszenie pracy programu) istnieją też dzo pożytecznymi klasami są QWaitCondition
go procesu (tego który go uruchomił). wady. Taką wadą jest konieczność dodatko- i QMutexLocker.
66 luty 2007
autorzy@lpmagazine.org
dla programistów
Programowanie równoległe z Qt
Program jako przykład store_max_size ) { (2) }
Rozważmy sytuację (często spotykana), że qDebug() << "> Dostawca: ide spac, const qint32 value = Shared::
jeden z wątków coś robi. Wynik swojej pracy nie ma miejsca na moje dane"; store.dequeue(); (5)
wkłada np. do kolejki (QQueue) o ograniczo- Shared::store_not_full.wait( qDebug() << "< Odbiorca: dostalem
nym rozmiarze. Ten wątek będziemy dalej na- &Shared::mutex ); (3) liczbe: " << value;
zywać Dostawcą. Na wyniki wykonanej pracy } Shared::store_not_full.wakeAll(); (6)
czeka inny wątek, zwany dalej Odbiorcą, który Shared::store.enqueue( --d_value );
chce je wykorzystać do swoich celów. Odbior- (4) Jak widać, ta część kodu dostępu do kolejki
ca po pobraniu danej z początku kolejki, usu- Shared::store_not_empty.wakeAll(); (5) także chroniona jest muteksem (1).
wa ją z niej. W języku wzorców projektowych Shared::mutex.unlock(); (6) Wywołanie konstruktora klasy QMutex-
wątki można nazwać odpowiednio: Producer Locker przełącza muteks w stan blokady (tak
i Consumer. W naszym przykładzie jeden wą- Jak widać, kod chroniony jest muteksem (1) jak mutex.lock()) (1). Najpierw sprawdzamy
tek będzie wkładał na koniec kolejki kolejne i (6). Na początku aktywujemy muteks, zapew czy w kolejce są jakieś dane (2 ).
liczby całkowite. niając sobie wyłączność na dostęp do kolej- Aby zapobiec 'zawiśnięciu' programu po
Musimy zapanować nad trzema sytuacja- ki (1). skończeniu pracy przez Dostawcę, wprowa-
mi: Następnie sprawdzamy czy kolejka jest dziłem możliwość, dzięki której Odbiorca mo-
pełna (2). Jeśli tak właśnie jest, Dostawca nie że sprawdzić czy Dostawca skończył swoja
" Dostawca i Odbiorca nie mogą pracować ma nic do roboty, nie ma miejsca na składo- pracę. Jeśli w kolejce nie ma danych i Dostawca
z kolejką jednocześnie, wanie kolejnych danych. W takiej sytuacji skończył pracę, to już nie ma na co czekać. Od-
" Dostawca musi wiedzieć, że w kolejce jest Dostawcy nie pozostaje nic innego, niż przej- biorca kończy działalność (3).
wolne miejsce (jest gdzie składować dane), ście w stan uśpienia i czekania na sygnał, Jeśli jednak Dostawca pracuje, a danych
" Odbiorca musi wiedzieć, że w kolejce coś oznaczający że miejsce już jest (3). Następ- nie ma (czyli Odbiorca jest szybszy od Dostaw-
jest (jest coś do odebrania) nie (jeśli jest miejsce w kolejce) dodajemy na cy), przechodzimy w stan uśpienia (5) i cze-
koniec kolejki kolejną liczbę (4). W kolejnym kamy na sygnał aktywujący. Jeśli w kolejce są
Dostawca i Odbiorca nie mogą pracować jed- kroku budzimy (ewentualnie) uśpiony wątek jakieś dane, to je odczytujemy (5). No i na koń-
nocześnie z kolejka. W tym celu zastosujemy Odbiorcy (5). No i na koniec znosimy blo- cu budzimy (ewentualnie) uśpiony wątek Dos-
klasę Qmutex. Dostawca musi być powiado- kadę, pozwalamy innym na dostęp do ko- tawcy (6). Na pierwszy rzut oka mogłoby się
miony, że w kolejce jest wolne miejsce, a Od- lejki (6). wydawać, że zapomnieliśmy odblokować mu-
biorca że jest coś do odebrania. Oczywiście Należy jednak zwrócić uwagę na jedna, teks. Ale to nieprawda. O odblokowanie mu-
mogliby, każdy w swojej pętli, sprawdzać cały bardzo istotną rzecz. Po uaktywnieniu mu- teksa zadba locker (QMutexLocker). Jest to
czas stan kolejki, ale obciążałoby to w znaczący teksa (1), jeśli wątek przejdzie w stan uśpie- klasa, która w swoim konstruktorze bloku-
sposób procesor, można by zaobserwować spo- nia (3) to mogłoby się wydawać, że program je muteks, a w destruktorze go odblokowuje.
re (lub nawet drastyczne) spowolnienie pracy zawiśnie . Przecież pozostał, wydawałoby Ponieważ locker został utworzony na stosie,
programu. Rozwiązaniem jest uśpienie wątku, się że na zawsze, aktywny, blokujący mu- podczas wychodzenia (w dowolnym miej-
który aktualnie nie może wykonywać swojej teks. Tak oczywiście nie jest. Do zmiennej sto- scu) z funkcji kończy sie zakres jego waż-
pracy. Każdy z wątków powinien zostać uak- re_not_full (typu QWaitCondition) przekazy- ności i automatycznie wywoływany jest je-
tywniony dopiero wtedy, gdy zajdą warunki wany jest adres muteksu. Właśnie po to, aby go destruktor. Jest to bardzo użyteczna kla-
sprzyjające wykonywaniu przez niego pracy. został on odblokowany. Czyli wątek czeka sa, szczególnie gdy funkcja ma kilka miejsc
Klasa która pozwala usypiać wątki i budzić je na sygnał pobudzający deaktywując muteks. wyjścia (return). Normalnie przed każdym
jeśli zajdzie określony warunek to QWaitCon- W momencie nadejścia sygnału i wyjścia z wyjść musielibyśmy ręcznie odblokowy-
dition. z funkcji wait() muteks będzie automaty- wać muteks (sytuacja podatna na błędy zwią-
UWAGA: Ponieważ musimy zsynchroni- cznie przywrócony do poprzedniego, czyli zane z nieuwagą programisty). Klasa Qmu-
zować dwie różne klasy-wątki, zmienne typu blokującego, stanu. texLocker i kompilator zrobią to za nas au-
QMutex i QWaitCondition nie mogą należeć Przyjrzyjmy się teraz kodowi Odbiorcy tomatycznie.
do żadnej z nich. Teoretycznie mogłyby być (plik odbiorca.cpp, funkcja read):
globalne, ale jako zdecydowany przeciwnik Podsumowanie
zmiennych globalnych umieszczę je w osobnej QMutexLocker locker( &Shared::mutex Jak widać używanie muteksów nie jest aż
klasie jako zmienne statyczne. ); (1) takie skomplikowane. Mam nadzieję, że czy-
W celach prezentacji współdziałania wąt- if( Shared::store.isEmpty() ) { (2) telnik po lekturze tego artykułu, podzieli ze
ków, do artykułu dołączono przykładowy pro- if( Shared::is_finished() ) { mną ten pogląd. Od programisty implemen-
gram o nazwie watki (projekt watki.pro). Moż- ... tacja wątków wymaga pewnego dodatko-
na go uruchomić na swoim komputerze i po- return; (3) wego nakładu pracy. Wzmożonej uwagi i kon-
obserwować jak wątki ze sobą współpracują. } centracji. Jednak pod względem koncep-
Tutaj chciałbym tylko omówić najbardziej isto- else { cyjnym używanie wątków nie jest zbyt trud-
tne elementy programu. Przeanalizujmy kod qDebug() << "< Odbiorca: ide ne. A nakład pracy może zwrócić się z na-
Dostawcy (plik dostawca.cpp, funkcja save): spac, brak danych"; wiązką.
Shared::store_not_empty.wait(
Shared::mutex.lock(); (1) &Shared::mutex ); (4)
if( Shared::store.size() == Shared:: }
www.lpmagazine.org 67
Wyszukiwarka
Podobne podstrony:
02 programowane tryby pracyJAVA 02 programowanie w systemie Linux2006 02 Program koncepcyjny02 Program dla Polski2007 02 SELinux – bardziej bezpieczny Linux [Bezpieczenstwo]2007 02 Szkoła konstruktorów2007 02 RetributionwareSIMR ALG1 EGZ 2007 02 08b rozwSIMR AN1 EGZ 2007 02 07b rozw2007 02 Mozliwosci wykorzystania masazu u dzieci z zab rozwojem psychomotorycznym cz 22007 02 Firewall leak testing [Consumer test]Magazine Ellery Queen Mystery Magazine 2007 02 February (v1 0) [html]Eko Świat Zwierzyniec terapeutyczny str 47 2007 02 47Eko Świat Zwierzyniec terapeutyczny str 47 2007 02 47więcej podobnych podstron