Rozdzia艂 20
, . , ,
z mowosc
Woaa
e
.. "
w k wosc
m I to
eo
Wielow膮tkowo艣膰 to cecha systemu operacyjnego polegaj膮ca na mo偶liwo艣ci jed-
noczesnego (wsp贸艂bie偶nego, r贸wnoleg艂ego) uruchamiania program贸w. W rzeczy-
I wisto艣ci system operacyjny komputera z jednym procesorem przydziela poszcze-
g贸lnym procesom (dzia艂aj膮cym wsp贸艂bie偶rue programom) "porcje czasu". Je偶eli
kwanty czasu s膮 wystarczaj膮co ma艂e - a komputer rue jest ob艂o偶ony zbyt wielo-
ma programami - u偶ytkownik mo偶e odnosi膰 wra偶erue, 偶e wszystkie programy
J dzia艂aj膮 r贸wnolegle.
i Wielozadaniowo艣膰 nie jest niczym nowym. W du偶ych komputerach typu main-
frame cecha ta jest powszechna. Do system贸w takich pod艂膮czone s膮 cz臋sto dzie-
si膮tki terminali, a ka偶dy u偶ytkowruk terminala ma wra偶enie, 偶e dysponuje zaso-
bami systemu na wy艂膮czno艣膰. Systemy operacyjne mainframe pozwalaj膮 r贸wnie偶
I
swoim u偶ytkownikom umieszcza膰 zadania w tle" dzie s kon ane rzez
" , g 膮 '㱮' 5'H' P
komputer w spos贸b nie anga偶uj膮cy uwagi u偶ytkownika, pozwalaj膮cy mu uru-
' chamia膰 inne zadania.
Wielozadaniowo艣膰 w komputerach osobistych pojawia艂a si臋 d艂ugo i rodzi艂a si臋
w b贸lach. Teraz cz臋sto uznajemy j膮 w komputerach PC za co艣 oczywistego. Jak
napisz臋 nieco dalej, 16-bitowe wersje Microsoft Windows obs艂ugiwa艂y wieloza-
daniowo艣膰, ale w bardzo ograniczonym zakresie. Wszystkie 32 bitowe wersje Win-
dows obs艂uguj膮 zar贸wno prawdziw膮 wielozadaniowo艣膰, jak i inne dobrodziej-
stwo - wielow膮tkowo艣膰.
Wielow膮tkowo艣膰 to wielozadaruowo艣膰 na poziomie jednego programu. Program
mo偶e by膰 podzielony na r贸wnolegle wykonywane podprogramy - w膮tki. Techni-
ka wielow膮tkowego dzia艂ania program贸w mo偶e pocz膮tkowo wydawa膰 si臋 ma艂o
przydatna, okazuje si臋 jednak, 偶e warto j膮 stosowa膰 podczas wykonywarua dhz-
go trwaj膮cych zada艅, aby nie zmusza膰 u偶ytkownika do robienia przerw w pracy.
Czasem oczywi艣cie mo偶e to by膰 niewskazane: wycieczka do wentylatora albo do
okna poprawia kr膮偶erue i sprzyja zdrowiu! Tak czy owak, u偶ytkownikowi nie
powinno si臋 odbiera膰 (cho膰by czasowo) mo偶liwo艣ci wykonywania 偶adnych za-
da艅 w jego komputerze, nawet gdy i tak wiadomo, 偶e z ruej nie skorzysta.
1066 Cz臋艣膰 III: Zagadnienia zaawansowane
Historia wielozadaniowo艣ci
Kiedy rodzi艂y si臋 komputery PC, wiele os贸b popiera艂o wielozadaniowo艣膰, ale te偶
wiele zadawa艂o sobie pytanie o przeciwnym wyd藕wi臋ku: po co wielozadanio-
wo艣膰 w komputerze osobistym przeznaczonym dla jednego u偶ytkownika? C贸偶,
okaza艂o si臋, 偶e wielozadaniowo艣ci chcieli nawet i ci u偶ytkownicy, kt贸rzy nie mieli
o niej zielonego poj臋cia.
wielozadaniowo艣膰 w systemie DOS?
Mikroprocesor Intel 8088, u偶ywany w pierwszych komputerach PC, nie by艂 pro-
jektowany jako serce systemu wielozadaniowego. Zasadniczy problem mia艂 zwi膮-
zek z niew艂a艣ciwym zarz膮dzaruem pami臋ci膮. Kiedy uruchamiane s膮 i wy艂膮czane
kolejne programy, wielozadaniowy system operacyjny powinien wkracza膰 do akcji
i przesuwa膰 (konsolidowa膰) bloki pami臋ciowe, zwalniaj膮c wi臋ksze ci膮g艂e obsza-
ry pami臋ci. Na 8088 nie mo偶na tego zrobi膰 w spos贸b przezroczysty dla aplikacji.
Sam DOS nie pom贸g艂. Zaprojektowany by艂 jako ma艂y system, kt贸ry mia艂 nie wcho-
dzi膰 w drog臋 aplikacjom; obs艂ugiwa艂 niewiele ponad podstawowe funkcje 艂ado-
wania program贸w i mechanizmy dost臋pu do systemu plik贸w.
Utalentowani programi艣ci z czas贸w, gdy na pecetach kr贸lowa艂 DOS, znale藕li jed-
nak pewien spos贸b pokonania jednozadaniowo艣ci: programy rezydentne (termi-
nate-and-stay-resident, TSR). Niekt贸re z nich, na przyk艂ad bufory drukowania,
podpina艂y si臋 pod przerwarua sprz臋towego zegara i w procedurach obshxgi prze-
rwa艅 wykonywa艂y zadania uruchomione w tle. Inne, takie jak SideKick, by艂y
w stanie wykonywa膰 pewnego rodzaju prze艂膮czanie zada艅 - zawiesza艂y dzia艂a-
nie aplikacji na czas uruchamiarua w艂asnego kodu. DOS zosta艂 r贸wnie偶 dobrze
wyposa偶ony w narz臋dzia wspomagaj膮ce prac臋 program贸w rezydentnych TSR.
Niekt贸rzy producenci podejmowali pr贸by opracowania w艂asnych pow艂ok wielo-
zadaniowych, dzia艂aj膮cych jako nak艂adki na DOS (przyk艂adem niech b臋dzie De-
sqView firmy Quarterdeck), ale tylko jeden z tych produkt贸w ostatecznie zyska艂
sobie du偶y udzia艂 w rynku. Chodzi oczywi艣cie o Windows.
wielozadaniowo艣膰 bez wyw艂aszczania
Kiedy Microsoft w roku 1985 wprowadzi艂 Windows 1.0, by艂o to najbardziej za-
awansowane rozwi膮zanie, jakie powsta艂o, maj膮ce wyrugowa膰 ograruczenia na-
k艂adane przez DOS. Wtedy te偶 Windows wkroczy艂 w 艣wiat trybu rzeczywistego
,
ale mimo to przenoszenie blok贸w pami臋ci w pami臋ci fizycznej - warunek reali-
zowania wielozadaniowego systemu operacyjnego - nie odbywa艂o si臋 ca艂kowi-
cie przezroczy艣cie dla aplikacji - niemniej by艂o zaimplementowane zno艣nie.
Wielozadaniowo艣膰 ma wi臋kszy sens w graficznym 艣rodowisku z oknami ni偶 w sys-
temie z lini膮 polece艅, na dodatek przeznaczonym dla tylko jednego u偶ytkowni-
ka. Na przyk艂ad w systemie UNIX, klasycznym systemie wielozadaniowym, z li-
rui polece艅 mo偶na uruchamia膰 programy dzia艂aj膮ce nast臋pnie w tle. Jednak wy-
nik dzia艂ania takiego programu musi by膰 kierowany do pliku, a nie na ekran, gdzie
utworzy艂by mozaik臋 z tym, co robi akurat u偶ytkownik.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1067
艢rodowisko z oknami umo偶liwia uruchamianie na jednym ekranie jednocze艣nie
wielu program贸w. Prze艂膮czanie si臋 tam i z powrotem staje si臋 trywialne; mo偶li-
we jest r贸wnie偶 szybkie przenoszenie danych mi臋dzy programami (obraz gra-
ficzny utworzony w programie rysunkowym mo偶na na przyk艂ad umie艣ci膰 w pli-
j ku tekstowym obs艂ugiwanym przez edytor tekst贸w). Transfer danych zosta艂
w Windows zrealizowany na r贸偶ne sposoby, pocz膮tkowo poprzez Schowek, po-
tem przez DDE (Dynamic Data Exclurnge), a obecnie przez OLE (Object Linking
and Embedding).
Wielozadaniowo艣膰 zaimplementowana we wczesnych wersjach Windows nie by艂a
jednak typowa, czyli taka jak w tradycyjnych systemach operacyjnych obs艂ugu-
j膮cych wielu u偶ytkownik贸w. W systemach tradycyjnych poszczeg贸lne zadania
by艂y co pewien interwa艂 (wyznaczany przerwaniami zegara sprz臋towego) prze-
rywane i prze艂膮czane. 16-bitowe wersje Windows by艂y wyposa偶one w co艣, co
nazywa艂o si臋 wielozadaniowo艣ci膮 bez wyw艂aszczania. Ten typ wielozadaniowo-
艣ci by艂 mo偶liwy do zrealizowania ze wzgl臋du na opart膮 na komunikatach archi-
tektur臋 Windows. W og贸lnym przypadku program uruchomiony w Windows
pozostaje u艣piony w pami臋ci, a偶 otrzyma jaki艣 komunikat. Komunikaty bywaj膮
bezpo艣rednimi i po艣rednimi wynikami dzia艂alno艣ci u偶ytkownika, a konkretnie
poruszania przez niego mysz膮 i naciskania klawiszy. Po "za艂atwieniu" komuni-
j katu program zwraca sterowanie do Windows.
16-bitowe wersje Windows nie prze艂膮cza艂y sterowarua arbitralnie mi臋dzy progra-
mami-klientami na podstawie przerwa艅 zegara. Prze艂膮czanie zada艅 odbywa艂o
si臋 wtedy, gdy dany program sko艅czy艂 obs艂ugiwanie otrzymanego komunikatu
i zwr贸ci艂 sterowanie do Windows. Ta technika wielozadaniowo艣ci bez wyw艂asz-
czania jest r贸wnie偶 nazywana "wielozadaniowo艣ci膮 kooperacyjn膮", poniewa偶
wymaga pewnej wsp贸艂pracy ze strony aplikacji. Jeden niepokorny program Win-
dows m贸g艂 zablokowa膰 ca艂y system, je偶eli zbyt d艂ugo przetwarza艂 jaki艣 komuni-
kat.
Cho膰 regu艂膮 w 16-bitowych wersjach Windows by艂a wielozadaniowo艣膰 bez wy-
w艂aszczania, systemy te mia艂y pewne zal膮偶ki form wielozadaruowo艣ci z wyw艂asz-
czaniem. W Windows wielozadaniowo艣膰 z wyw艂aszczaniem s艂u偶y艂a do urucha-
miania program贸w DOS i by艂a dozwolona bibliotekom dynamicznym, kt贸re mog艂y
odbiera膰 przerwania zegarowe i za ich pomoc膮 synchronizowa膰 multimedia.
16-bitowe wersje Windows zawiera艂y kilka funkcji pomocnych programistom przy
rozwi膮zywaniu problem贸w zwi膮zanych z wielozadaniowo艣ci膮 bez wyw艂aszcza-
nia - a przynajmniej eliminowaniu pewnych mniejszych jej uci膮偶liwo艣ci. Naj-
wi臋kszym utrapieniem u偶ytkownik贸w by艂 wska藕nik myszy z klepsydr膮. Nie by艂o
to, oczywi艣cie, 偶adne rozwi膮zanie, ale spos贸b informowania u偶ytkownika o tym,
偶e program jest zaj臋ty i 偶e pracuje nad powa偶niejszym zadaniem, czytaj: b臋dzie
przez d艂u偶sz膮 chwil臋 niedost臋pny. Inn膮 prowizork臋 Windows, zegar, mo偶na by艂o
zaprogramowa膰 tak, aby jaki艣 program otrzyma艂 komunikaty w okre艣lonych in-
terwa艂ach. Zegar cz臋sto wykorzystywano do synchronizowania aplikacji wyko-
rzystuj膮cych animacj臋.
Innym rozwi膮zaruem 艂ataj膮cym dziury systemu z wielozadaniowo艣ci膮 bez wy-
w艂aszczania jest funkcja PeekMessage, przedstawiona w rozdziale 5, w programie
1068 Cz臋艣膰 III: Zagadnienia zaawansowane
RANDRECT. Program do odbierania kolejnych komurukat贸w z kolejki u偶ywa
zazwyczaj wywo艂arua GetMessage. Je偶eli jednak nie ma 偶adnych kolejnych komu-
nikat贸w, funkcja GetMessage nie ko艅czy swojego dzia艂ania, blokuj膮c ca艂y program
do czasu pojawienia si臋 jakiego艣 komunikatu. Antidotum okazuje si臋 w艂a艣nie
funkcja PeekMessage, kt贸ra zwraca sterowarue do programu niezale偶nie od tego,
czy jaki艣 komunikat zosta艂 odebran cz nie. Pro ram mo偶e wi c w kon a膰 I
y y g 臋 y yw
d艂ugie zadania i w艂膮cza膰 w nie wywo艂arua PeekMessage. W ten spos贸b w czasie,
gdy nie ma 偶adnych komunikat贸w, kt贸rymi mia艂by si臋 zaj膮膰 program, mog膮 by膰
wykonywane jakie艣 czasoch艂onne zadarua.
Mened偶er prezentacji i szeregowa kolejka k贸munikat贸w
Pierwsz膮 podj臋t膮 przez Microsoft (we wsp贸艂pracy z firm膮 IBM) pr贸b膮 zaimple-
mentowania wielozadaniowo艣ci w 艣rodowisku DOS/Windows by艂 system OS/2
i jego sk艂adnik Presentation Manager (PM - Mened偶er prezentacji). System OS/2
obs艂ugiwa艂 rzeczywist膮 wielozadaniowo艣膰 z wyw艂aszczaruem, cz臋sto wydawa艂o
si臋, 偶e tym wyw艂aszczaruem obarczono program Presentation Manager. Problem
polega艂 na tym, 偶e PM szeregowa艂 komunikaty wej艣ciowe z klawiatury i myszy,
tworz膮c szeregow膮 kolejk臋 komunikat贸w (ang. serialized message guegue). Ozna-
cza to, 偶e nie m贸g艂 dostarcza膰 komunikat贸w z klawiatury ani z myszy do pro-
gramu, dop贸ki obs艂uga poprzedruego komunikatu wej艣ciowego nie zako艅czy艂a
si臋 sukcesem.
Cho膰 komunikaty klawiatury i myszy stanowi膮 tylko cz臋艣膰 wszystkich komuni-
kat贸w, jakie program PM (lub Windows) mo偶e otrzyma膰, wi臋kszo艣膰 pozosta艂ych
stanowi艂y komunikaty b臋d膮ce po艣redriim skutkiem zdarze艅 zwi膮zanych z kla-
wiatur膮 i mysz膮. Na przyk艂ad komunikat wybrania polecenia menu jest skutkiem
tego, 偶e u偶ytkownik wybra艂 opcj臋 menu, a m贸g艂 to zrobi膰 tylko za pomoc膮 my-
szy b膮d藕 klawiatury. Komunikat klawiatury lub myszy nie b臋dzie wi臋c w pe艂ni
obs艂u偶ony, dop贸ki nie zostarue obs艂u偶ony komunikat polecenia menu.
G艂贸wnym powodem szeregowania komunikat贸w w kolejce by艂o umo偶liwienie
przewidywania operacji wykonywanych przez u偶ytkownika za pomoc膮 klawia-
tury (type-ahead) i myszy (mouse-ahead). Je偶eli jeden z komunikat贸w klawiatury
lub myszy powodowa艂 prze艂膮czenie fokusu z jednego okna do drugiego, kolejne
komunikaty klawiaturowe powinny by艂y trafia膰 do tego, kt贸re otrzyma艂o fokus.
Jak wida膰, system nie wie, gdzie ma wys艂a膰 kolejne komunikaty wej艣ciowe od
u偶ytkownika, dop贸ki nie obs艂u偶y wszystkich wcze艣ruejszych.
Dzi艣 rueod艂膮cznym atrybutem dobrego systemu operacyjnego jest to, 偶e 偶adna
pojedyncza aplikacja rue mo偶e zablokowa膰 ca艂ego systemu, a to wymaga niesze-
regowej kolejki komunikat贸w - takiej, jak膮 zrealizowano w 32-bitowych wersjach
Windows. Je偶eli jaki艣 program jest zaj臋ty wykonywaniem czasoch艂onnego zada-
nia, fokus wej艣ciowy mo偶na prze艂膮czy膰 na inny.
Rozwi膮zanie wielow膮tkowe
Wspomina艂em o Mened偶erze prezentacji z systemu OS/2 tylko dlatego, 偶e sta-
nowi艂 on 艣rodowisko, kt贸re zapewru艂o weteranom programowania w Windows
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1069
(do jakich si臋 zaliczam) pierwszy kontakt z wielow膮tkowo艣ci膮. Najciekawsze, 偶e
w艂a艣nie ograniczenia implementacji Mened偶era prezentacji w dziedzinie wielo-
w膮tkowo艣ci da艂y programistom do my艣lenia i 偶e za ich spraw膮 opracowano za-
艂o偶enia, jakie musi spe艂nia膰 poprawnie funkcjonuj膮ca architektura program贸w
wielow膮tkowych. Cho膰 ograniczenia te zosta艂y w du偶ej mierze wyeliminowane
w 32-bitowych wersjach Windows, wnioski wyci膮gni臋te na podstawie dzia艂ania
tych prostszych 艣rodowisk operacyjnych s膮 wci膮偶 bardzo na czasie. Zacznijmy
wi臋c.
W 艣rodowisku wielow膮tkowym programy mog膮 si臋 dzieli膰 na osobne kawa艂ki
,
nazywane "w膮tkami" i dzia艂aj膮ce wsp贸艂bie偶nie. Obs艂uga w膮tk贸w okaza艂a si臋
najlepszym lekarstwem na szeregowanie kolejki komunikat贸w w Mened偶erze
prezentacji i obecnie spe艂nia wa偶n膮 rol臋 w Windows.
Z punktu widzenia programisty w膮tek jest reprezentowany przez funkcj臋, kt贸ra
mo偶e r贸wnie偶 wywo艂ywa膰 inne funkcje programu. Program zaczyna dzia艂anie
jako g艂贸wny (i pocz膮tkowo jedyny) w膮tek, kt贸ry w tradycyjnym programie C jest
funkcj膮 main, a w Windows WinMain. Dzia艂aj膮cy ju偶 program mo偶e powo艂ywa膰
do 偶ycia nowe w膮tki, wywo艂uj膮c specjaln膮 funkcj臋 (CreateThread), kt贸rej podaje
nazw臋 pierwszej funkcji Thread. System operacyjny prze艂膮cza sterowanie mi臋dzy
w膮tkami na zasadach wyw艂aszczania, podobnie jak w rasowym systemie z prze-
艂膮czaniem zada艅.
W Mened偶erze prezentacji systemu OS/2 ka偶dy w膮tek mo偶e utworzy膰 kolejk臋
komunikat贸w, ale nie musi. W膮tek PM musi utworzy膰 kolejk臋, je偶eli chce, aby
z tego w膮tku by艂y tworzone okna. W przeciwnym razie (je偶eli program mia艂 na
przyk艂ad tylko prowadzi膰 jakie艣 obliczerua albo wy艣wietla膰 grafik臋) tworzenie
kolejki jest zb臋dne. Poniewa偶 w膮tki bez kolejki komunikat贸w nie zajmuj膮 si臋 ob-
s艂ug膮 komunikat贸w, nie mog膮 zawiesi膰 systemu. Jedynym ograniczeniem jest to,
偶e pozbawiony kolejki w膮tek nie mo偶e wysy艂a膰 komunikatu do okna utworzo-
nego w w膮tku z kolejk膮 ani wywo艂a膰 偶adnej funkcji powoduj膮cej wys艂anie takie-
go komunikatu (mo偶e jednak wysy艂a膰 komunikaty do samego w膮tku z kolejk膮).
Tak wi臋c programi艣ci pisz膮cy "pod Mened偶er prezentacji" nauczyli si臋 dzieli膰 swoje
programy na jeden w膮tek z kolejk膮 komunikat贸w, kt贸ry otwiera艂 wszystkie okna
i przetwarza艂 zwi膮zane z nimi komunikaty, oraz jeden lub wi臋cej w膮tk贸w bez w艂a-
snych kolejek komurukat贸w, przeznaczonych do wykonywania czasoch艂onnych
operacji w tle. Programi艣ci ci nauczyli si臋 r贸wnie偶 stosowa膰 zasad臋 "jednej dziesi膮-
tej sekundy", m贸wi膮c膮, 偶e w膮tek z kolejk膮 komurukat贸w nie powiruen przezna-
cza膰 wi臋cej czasu na przetwarzanie komunikat贸w ni偶 jedna dziesi膮ta sekundy.
Wszystko, co ma trwa膰 d艂u偶ej, powinno by膰 wykonywane w osobnym w膮tku. Je-
偶eli wszyscy programi艣ci post臋powaliby zgodnie z t膮 regu艂膮, 偶aden program Me-
ned偶era prezentacji nie zawiesi艂by systemu na d艂u偶ej ni偶 jedna dziesi膮ta sekundy.
Architektura wielow膮tkowa
Napisa艂em, 偶e ograniczenia Mened偶era prezentacji dostarczy艂y programistom
cennych wskaz贸wek, jak pos艂ugiwa膰 si臋 w膮tkami w programach dzia艂aj膮cych
w 艣rodowiskach graficznych. Oto co polecam jako architektur臋 dla program贸w
wielow膮tkowych: w膮tek g艂贸wny powinien tworzy膰 wszystkie okna u偶ywane w
1070 Cz臋艣膰 III: Zagadnienia zaawansowane
programie, 艂膮cznie ze wszystkimi stosownymi procedurami okien, i przetwarza膰
wszystkie zwi膮zane z nimi komunikaty. Reszt膮 dzia艂ania programu powinny
zajmowa膰 si臋 pozosta艂e w膮tki, przy czym nie mog膮 one wymienia膰 informacji
z u偶ytkownikiem, chyba 偶e poprzez komunikacj臋 z w膮tkiem g艂贸wnym.
Ujmuj膮c to prosto, g艂贸wny w膮tek ma si臋 zajmowa膰 obs艂ug膮 wej艣cia od u偶ytkow-
nika (i innymi komunikatami), tworz膮c ewentualnie w膮tki poboczne, realizuj膮ce
konkretne zadania. Owe w膮tki poboczne maj膮 pe艂ni膰 funkcje niezwi膮zane z za-
daniami u偶ytkownika.
Inaczej m贸wi膮c, w膮tek g艂贸wny programu jest kierownikiem, a w膮tki poboczne
jego pracownikami. Kierownik zleca wszystkie powa偶ne zadania swoim pracow-
nikom, utrzymuj膮c kontakt z nimi i ze 艣wiatem zewn臋trznym. Pracownicy nie
mog膮 jednak prowadzi膰 w艂asnych konferencji prasowych. Sumiennie i z dala od
fleszy aparat贸w reporterskich wykonuj膮 zlecone przez szefa prace, a kiedy je
ko艅cz膮, czekaj膮 na nowe.
W膮tki stanowi膮 integraln膮 cz臋艣膰 programu, wi臋c mog膮 korzysta膰 z jego zasob贸w,
takich jak pami臋膰 i otwarte pliki. Mog膮 u偶ywa膰 r贸wnie偶 wsp贸lnych zmiennych
statycznych. Ka偶dy z nich ma jednak sw贸j w艂asny stos, dlatego zmienne automa-
tyczne s膮 prywatne dla ka偶dego w膮tku. Ka偶dy w膮tek ma sw贸j w艂asny rekord ze
stanem procesora (oraz rekord ze stanem koprocesora), zapisywany i odtwarza-
ny podczas prze艂膮czania w膮tk贸w.
Uci膮偶liwo艣膰 w膮tk贸w
Poprawne zaprojektowanie, zakodowanie i zdebugowanie skomplikowanej apli-
kacji wielow膮tkowej to z pewno艣ci膮 jedno z najtrudniejszych zada艅, przed jaki-
mi staj膮 programi艣ci. Poniewa偶 wielozadaniowy system z wyw艂aszczaniem mo偶e
przerwa膰 ka偶dy w膮tek w dowolnym punkcie i przekaza膰 sterowanie do innego
w膮tku, wszelkie niepo偶膮dane interakcje mi臋dzy oboma w膮tkami mog膮 ujawnia膰
si臋 nie od razu, losowo i efemerycznie.
Jeden z cz臋stszych b艂臋d贸w wyst臋puj膮cych w programach wielow膮tkowych jest
nazywany wy艣cigami. Do wy艣cig贸w dochodzi wtedy, gdy programista zak艂ada,
偶e jaki艣 w膮tek uko艅czy prac臋 nad czym艣 - na przyk艂ad nad przygotowywaniem
danych - zanim inny w膮tek b臋dzie owych danych potrzebowa艂. Aby pom贸c
w koordynacji dzia艂a艅 w膮tk贸w, systemy operacyjne wymagaj膮 r贸偶nych form syn-
chronizacji. Jednym z narz臋dzi s艂u偶膮cych synchronizacji jest tzw. semafor. Pozwala
on programi艣cie blokowa膰 wykonanie jakiego艣 w膮tku w okre艣lonym punkcie kodu
do czasu, kiedy inny w膮tek da mu na to pozwolenie. Do semafor贸w podobne s膮
sekcje krytyczne - fragmenty kodu, kt贸rych system operacyjny nie mo偶e prze-
rwa膰.
Z semaforami wi膮:e si臋 jeszcze inne niebezpiecze艅stwo: zakleszczenie. Docho-
dzi do niego wtedy, gdy dwa w膮tki blokuj膮 si臋 wzajemnie i ka偶dy m贸g艂by zosta膰
odblokowany tylko wtedy, gdyby drugi si臋 wykona艂.
Na szcz臋艣cie programy 32-bitowe s膮 bardziej odporne na pewne problemy zwi膮-
zane z w膮tkami ni偶 16-bitowe. Za艂贸偶my, 偶e jaki艣 w膮tek wykonuje prost膮 instruk-
c7臋
lCount++;
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1071
gdzie lCount jest 32-bitow膮 zmienn膮 globaln膮 u偶ywan膮 przez inny w膮tek. W pro-
gramie 16-bitowym ta pojedyncza instrukcja C jest kompilowana na dwie instruk-
cje kodu maszynowego: pierwsza zwi臋ksza m艂odsze 16 bit贸w zmiennej, a druga
jej starsze 16 bit贸w. Za艂贸偶my dalej, 偶e system operacyjny przerwa艂 w膮tek w艂a-
艣nie w punkcie mi臋dzy tymi dwoma instrukcjami. Je偶eli zmienna lCount mia艂aby
pocz膮tkowo warto艣膰 0x0000FFFF, to po wyst膮pieniu przerwania w膮tku jej war-
to艣膰 wynios艂aby zero - przynajmniej tak膮 warto艣膰 odczyta艂by inny w膮tek. Po
wznowieniu przerwanego w膮tku program doko艅czy艂by zwi臋kszanie lCount, kt贸ra
ostatecznie przyj臋艂aby poprawn膮 warto艣膰 0x00010000.
To by艂 przyk艂ad jednego z b艂臋d贸w, kt贸re mog膮 powodowa膰 problemy tak rzadko,
偶e mog膮 d艂ugo umyka膰 niezauwa偶one. W programie 16-bitowym w艂a艣ciwym spo-
sobem rozwi膮zania tego problemu jest umieszczenie instrukcji inkrementacyjnej
w sekcji krytycznej (podczas kt贸rej w膮tek nie mo偶e by膰 przerywany). W progra-
mie 32-bitowym instrukcja inkrementacji mo偶e pozosta膰 bez zmian, poniewa偶 w
wyniku kompilacji powstaje tylko jedna instrukcja kodu maszynowego.
Przewaga Windows
32-bitowe wersje Windows (w tym Microsoft Windows NT i Windows 98) maj膮 nie-
szeregowe kolejki komunikat贸w. Implementacje tych kolejek s膮 bardzo dobre: gdy
obs艂uga komunikatu zajmuje programowi wi臋cej czasu, wska藕nilc myszy zmienia si臋
w klepsydr臋, je艣li znajduje si臋 nad obszarem roboczym okna programu, albo pozo-
staje normaln膮 strza艂k膮, je艣li znajduje si臋 nad obszarem innego okna programu. To
drugie okno mo偶na przenie艣膰 na pierwszy plan za pomoc膮 prostego klikni臋cia.
U偶ytkownik nadal jednak nie mo偶e pracowa膰 z programem wykonuj膮cym cza-
soch艂onne zadanie, poniewa偶 nie pozwala ono programowi odbiera膰 innych ko-
t munikat贸w. Jest to efekt niepo偶膮dany. Program powinien by膰 zawsze otwarty na
komunikaty, a to cz臋sto wymaga stosowania w膮tk贸w pobocznych.
( W Windows NT i Windows 98 nie ma rozr贸偶nienia mi臋dzy w膮tkami z kolejk膮
i w膮tkami bez kolejki. Ka偶dy w膮tek otrzymuje swoj膮 kolejk臋 w chwili, gdy po-
wstaje. Eliminuje to pewne dziwne regu艂y obowi膮zuj膮ce w膮tki z program贸w PM.
W wi臋kszo艣ci przypadk贸w komunikaty wej艣ciowe i tak s膮 przetwarzane za po-
moc膮 procedur jednego w膮tku, a zadania czasoch艂onne - delegowane do innych
w膮tk贸w, tych nie obs艂uguj膮cych okien. Jak si臋 wkr贸tce oka偶e, taka struktura w
wi臋kszo艣ci przypadk贸w si臋 sprawdza.
A oto i wi臋cej dobrych wie艣ci: Windows NT i Windows 98 zawieraj膮 funkcj臋
umo偶liwiaj膮c膮 jednemu w膮tkowi zabicie innego w膮tku tego samego procesu. Jak
si臋 oka偶e podczas pisania kodu wielow膮tkowego, jest to czasem wygodne. Pierw-
sze wersje OS/2 nie zawiera艂y funkcji typu "zabij w膮tek".
( Ostatni膮 dobr膮 nowin膮 (przynajmniej na ten temat) jest to, 偶e w Windows NT
i Windows 98 zaimplementowano lokaln膮 pami臋膰 w膮tku (thread local storage, TLS).
Aby to poj膮膰, przypomnij sobie, jak pisa艂em wcze艣niej, 偶e zmienne statyczne, za-
r贸wno globalne, jak i lokalne dla funkcji, s膮 wsp贸lne dla w膮tk贸w, poniewa偶 s膮
ulokowane w przestrzeni adresowej procesu. Zmienne automatyczne (kt贸re s膮
zawsze lokalne dla funkcji) s膮 prywatne dla ka偶dego w膮tku, poniewa偶 zajmuj膮
przestrze艅 na stosie, a ka偶dy w膮tek ma sw贸j osobny stos.
T
Cz臋艣膰 III: Zagadnienia zaawansowane
Czasem jest wygodnie, gdy kilka w膮tk贸w korzysta z tej samej funkcji; dobrze jest
r贸wnie偶, gdy w膮tki wykorzystuj膮 zmienne statyczne prywatne dla w膮tk贸w. Tak膮
funkcj臋 pe艂ru w艂a艣nie pami臋膰 lokalna. Wi膮偶e si臋 z ni膮 kilka nowych funkcji Win-
dows, ale Microsoft doda艂 r贸wnie偶 do kompilatora C rozszerzenie, dzi臋ki kt贸re-
mu korzystanie z TLS jest bardziej przezroczyste dla programisty.
Nowo艣! Ulepszona formu艂a! Dost臋pne z w膮tkami!
Teraz, kiedy ju偶 rozdmucha艂em kwestie w膮tk贸w, umie艣膰my je w odpowiedniej
perspektywie. Niekt贸rzy programi艣ci maj膮 tendencj臋 do u偶ywania ka偶dej funk-
cji, jak膮 dany system operacyjny ma do zaoferowania. Z gorszym przypadkiem
masz do czynienia, kiedy do twojego biurka podchodzi szef i m贸wi "S艂ysza艂em,
偶e ta nowa funkcja Jakjejtam jest naprawd臋 艣wietna. Mo偶e by艣my zastosowali j膮
w naszym programie?". Sp臋dzasz wi臋c ca艂y tydzie艅 na rozpracowaniu, co do-
brego funkcja jakjejtam mo偶e ewentualnie zrobi膰 dla twojej aplikacji.
Chodzi o to, 偶e nie ma sensu wyposa偶a膰 w wielow膮tkowo艣膰 programu, kt贸ry si臋
艣wietnie sprawuje bez niej. Niekt贸re aplikacje po prostu nie nadaj膮 si臋 do stoso-
wania rozwi膮za艅 wielow膮tkowych. Je偶eli tw贸j program wy艣wietla wska藕nik
myszy z klepsydr膮 przez niepokoj膮co dhxgi czas albo je偶eli pos艂uguje si臋 wywo-
艂aniem PeekMessage w celu unikni臋cia wska藕nika z klepsydr膮, w贸wczas zrefor-
mowanie go i wyposa偶enie w w膮tki b臋dzie z pewno艣ci膮 rozs膮dn膮 propozycj膮.
W przeciwnym jednak razie po prostu utrudniasz sobie 偶ycie i ca艂kiem mo偶liwe,
偶e wprowadzasz do kodu nowe b艂臋dy.
Istnieje nawet kilka przypadk贸w, kiedy wska藕nik z klepsydr膮 mo偶e by膰 ca艂ko-
wicie uzasadniony. Wspomnia艂em wcze艣niej o zasadzie jednej dziesi膮tej sekun-
dy. C贸偶, 艂adowanie du偶ego pliku do pami臋ci zajmuje wi臋cej ni偶 jedn膮 dziesi膮t膮
sekundy. Czy oznacza to zatem konieczno艣膰 tworzenia osobnego w膮tku 艂aduj膮-
cego pliki? Niekoniecznie. Kiedy u偶ytkownik nakazuje programowi otworzy膰 plik,
chce zazwyczaj, aby 偶膮dana operacja zosta艂a zrealizowana od razu. Umieszcze-
nie procedur 艂aduj膮cych pliki w osobnych w膮tkach spowodowa艂oby zb臋dne op贸藕-
nienia. Po prostu nie warto, nawet je偶eli chcesz si臋 pochwali膰 przed kolegami, 偶e
piszesz programy wielow膮tkowe!
Wielow膮tkowo艣膰 w Windows
Funkcja API tworz膮ca nowy w膮tek nosi nazw臋 CreateThread. Ma ona nast臋puj膮c膮
sk艂adni臋:
hThread = CreateThread (&security "ttributes, dwStackSize, ThreadProc, pParam,
dwFlags, &idThread):
Jej pierwszym argumentem jest wska藕nik do struktury typu SECURIT'Y嗀TTRI-
BUT'ES. Ten argument jest ignorowany w Windows 98. Mo偶na mu r贸wnie偶 nada膰
warto艣膰 NULL w Windows NT. Drugi argument jest pocz膮tkow膮 wielko艣ci膮 sto-
su dla nowego w膮tku; mo偶na mu przypisa膰 0, aby otrzyma膰 warto艣膰 domy艣ln膮.
Windows i tak dynamicznie modyfikuje d艂ugo艣膰 stosu, je偶eli to konieczne.
Trzeci argument CreateThread to wska藕nik do funkcji Thread. Funkcja mo偶e mie膰
dowoln膮 nazw臋, ale musi mie膰 sk艂adni臋:
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1073
DWORD WINAPI ThreadProc (PVOID pParam);
Czwarty argument CreateThread staje si臋 argumentem ThreadProc. Oto jak w膮tek
g艂贸wny mo偶e wsp贸艂u偶ytkowa膰 dane.
Pi膮tym argumentem CreateThread jest z regu艂y 0, ale mo偶na poda膰 znacznik CRE-
AT臉 SUSPENDED, je偶eli w膮tek ma zosta膰 utworzony, ale nie od razu urucho-
miony. W膮tek pozostanie zawieszony do czasu wywo艂ania ResumeThread. Sz贸sty
argument to wska藕nik do zmiennej, kt贸ra otrzyma warto艣膰 identyfikatora w膮t-
ku.
Wi臋kszo艣膰 programist贸w Windows woli u偶ywa膰 funkcji beginthread z biblioteki
czasu wykonywania j臋zyka C, deklarowanej w pliku nag艂贸wkowym PROCESS.H.
Funkcja ma nast臋puj膮c膮 sk艂adni臋:
hThread = beginthread (ThreadProc, uiStackSize, pParam);
Jest ona nieco prostsza i w wi臋kszo艣ci aplikacji ca艂kowicie wystarczaj膮ca. Funk-
cja Thread ma nast臋puj膮c膮 sk艂adru臋:
void 㧟decl ThreadProc (void * pParam);
Losowe prostok膮ty raz jeszcze
Program RNDRCTMT pokazany na rysunku 20-1 jest wielow膮tkow膮 wersj膮 pro-
gramu RANDRECT z rozdzia艂u 5. Jak pami臋tamy, w RANDRECT u偶yta zosta艂a
p臋tla PeekMessage, w kt贸rej wy艣wietlany by艂 ci膮g losowych prostok膮t贸w.
RNDRCTMT.C
/*
*/
include
include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HWND hwnd ;
int cxClient, cyClient ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
RNDRCTMT.C - Losowe prostokdty
(c) Charles Petzold, 1998
static TCHAR szAppNameC) = TEXT ("RndRctMT")
MSG msg ;
WNDCLASS wndclass :
wndclass.style = CS_HREDRAW CS㑇REDRAW
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION)
wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ;
1074 Cz臋艣膰 III: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1073)
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHIT臉 BRUSH) ;
wndclas5.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
i
Messa9eBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB蘒CONERROR) ;
return 0 ;
)
hwnd = CreateWindow (szAppName, TEXT ("Random Rectangles"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,. ,
NULL, NULL, hInstance, NULL) :
ShowWindow (hwnd, iCmdShow) ; '
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ; i
DispatchMessage (&msg) ;
)
return msg.wParam ;
)
VOID Thread (PVOID pvoid)
(
HBRUSH hBrush ;
HDC hdc ; '
int xLeft, xRi9ht, yTop, yBottom, iRed, iGreen, iBlue ;
while (TRUE)
(
if (cxClient != 0 cyClient != 0)
(
xLeft = rand () % cxClient :
xRight = rand () % cxClient ;
yTop = rand () % cyClient ;
yBottom = rand () % cyClient ;
iRed = rand () & 255 ;
iGreen = rand () & 255 :
iBlue = rand () & 255 :
hdc = GetDC (hwnd) ;
hBrush = CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;
SelectObject (hdc, hBrush) ; 艂
Rectangle (hdc, min (xLeft. xRight), min (yTop, yBottom),
max (xLeft, xRight), max (yTop, yBottom)) ;
ReleaseDC (hwnd, hdc) ;
Delete0bject (hBrush) ;
)
,
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1075
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
switch (message)
(
case WM_CREATE:
㧏eginthread (Thread, 0, NULL) ;
return 0 :
case WM SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 :
case WM DESTROY:
PostQuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
)
Rysunek 20-1. Program RNDRCTMT
Za ka偶dym razem, kiedy przygotowuje si臋 wielow膮tkowy program Windows,
trzeba zmieni膰 pewne opcje w oknie dialogowym ustawie艅 projektu. Uaktywnij
kart臋 C/C++ i wybierz opcj臋 Code Generation z pola Category. W polu Run-Time
Library powinny si臋 pojawi膰 konfiguracje Single-Threaded for the Release i De-
bug Single-Threaded for the Debug. Zmie艅 je odpowiednio na Multithreaded i De-
bug Multithreaded. Spowoduje to zmian臋 opcji kompilatora na /MT, niezb臋dn膮
do kompilowania aplikacji wielow膮tkowych. Uruchomiony z t膮 opcj膮 kompila-
tor wstawia w pliku OBJ odniesienie do pliku LIBCMT.LIB, a nie do LIBC.LIB.
Konsolidator u偶ywa tej nazwy do konsolidacji z funkcjami bibliotecznymi czasu
wykonywania.
Pliki LIBC.LIB i LIBCMT.LIB zawieraj膮 funkcje biblioteczne C. Niekt贸re z nich
maj膮 w艂asne dane statyczne. Na przyk艂ad funkcja strtok jest pomy艣lana tak, aby
j膮 wywo艂ywa膰 kilka razy z rz臋du, i pami臋ta wska藕nik do pami臋ci statycznej. Dla-
tego ka偶dy w膮tek programu wielow膮tkowego musi mie膰 w funkcji strtok w艂asny
wska藕nik statyczny. Wielow膮tkowa wersja tej funkcji strtok b臋dzie wi臋c nieco inna
ni偶 jednow膮tkowa.
Mo偶na r贸wnie偶 zauwa偶y膰, 偶e w pliku RNDRCTMT.C w艂膮czany jest plik nag艂贸w-
kowy PROCESS.H, zawieraj膮cy deklaracj臋 funkcji beginthread, kt贸ra uruchamia
nowy w膮tek. Funkcja ta nie jest deklarowana, je偶eli nie zostanie zdefiniowany
identyfikator MT, a to z kolei jest skutkiem u偶ycia opcji /MT kompilatora.
Warto艣膰 hwnd zwr贸cona z CreateWindow w funkcji WinMain pliku RNDRCTMT.C
zapisywana jest w zmiennej globalnej. To samo dotyczy warto艣ci cxClient i cyClient
uzyskiwanych z komunikatu WMIZE w procedurze okna.
Procedura okna wywo艂uje beginthread w najprostszy mo偶liwy spos贸b, czyli tyl-
ko z adresem funkcji w膮tkowej (o nazwie Thread) jako pierwszym argumentem
i z zerami jako argumentami pozosta艂ymi. Funkcja w膮tkowa zwraca VOID i ma
argument b臋d膮cy wska藕nikiem do VOID. Funkcja Thread z RNDRCTMT nie ma
tego argumentu.
T
1076 Cz臋艣膰 III: Zagadnienia zaawansowane
Od chwili wywo艂ania beginthread kod tej funkcji (razem z kodem z innych wy-
wo艂ywanych ewentualnie przez ni膮 funkcji) rozpoczyna dzia艂anie wsp贸艂bie偶nie
z pozosta艂ym kodem programu. Tej samej funkcji mo偶e u偶ywa膰 kilka w膮tk贸w tego
samego procesu. W tym przypadku automatyczne zmienne lokalne (pami臋tane
na stosie) s膮 prywatne dla ka偶dego w膮tku; wszystkie za艣 zmienne statyczne s膮
wsp贸lne wszystkim w膮tkom danego procesu. W taki w艂a艣nie spos贸b procedura
okna mo偶e nadawa膰 warto艣ci zmiennym globalnym cxClient i cyClient, a funkcja
Thread z nich korzysta膰.
Czasem potrzebne s膮 trwa艂e dane prywatne dla wi臋cej ni偶 jednego w膮tku. Zwy-
kle trwa艂e dane s膮 zwi膮zane ze zmiennymi statycznymi, ale w Windows 98 ist-
nieje mo偶liwo艣膰 korzystania z danych TLS, o kt贸rych wspomnia艂em i kt贸re om贸-
wi臋 szczeg贸艂owiej w dalszej cz臋艣ci tego rozdzia艂u.
Zadanie z konkursu programistycznego Microsoftu
Trzeciego pa藕dziemika 1986 roku firma Microsoft zorganizowa艂a trwaj膮cy ca艂y dzie艅
briefing dla redaktor贸w technicznych oraz autor贸w ksi膮偶ek i artyku艂贸w do maga-
zyn贸w informatycznych. Konferencja po艣wi臋cona by艂a prezentacji bie偶膮cych pro-
dukt贸w j臋zyk贸w programowania, mi臋dzy innymi pierwszemu interakcyjnemu 艣ro-
dowisku programistycznemu QuickBASIC 2.0. Windows 1.0 mia艂 wtedy mniej ni偶
rok i nikt nie wiedzia艂, czy kiedykolwiek pojawi si臋 analogiczny produkt dla tego
艣rodowiska (trzeba by艂o poczeka膰 kilka lat). Na konferencji mia艂o miejsce jedno cie-
kawe wydarzenie: pracownicy public relations Microsoftu przygotowali tumiej pro-
gramistyczny pod tytu艂em "Storm the Gates" (pokonaj Gatesa). Bill Gates mia艂
u偶ywa膰 programu QuickBASIC 2.0, a ochotnicy rekrutowani spo艣r贸d uczestnik贸w
konferencji mieli do dyspozycji dowolny przyniesiony przez siebie produkt.
Zadanie konkursowe zosta艂o wyj臋te z kapelusza, a 艣ci艣lej wylosowane spo艣r贸d
kilku przedstawionych przez uczestnik贸w. Mia艂o zaj膮膰 oko艂o p贸艂 godziny pro-
gramowania, a brzmia艂o mniej wi臋cej tak:
Napisz wielow膮tkow膮 symulacj臋 z艂o偶on膮 z czterech okien. W pierwszym oknie
nale偶y wy艣wietla膰 seri臋 rosn膮cych liczb, w drugim seri臋 rosn膮cych liczb pierw-
szych, a w trzecim warto艣ci ci膮gu Fibonacciego. Ci膮g Fibonacciego zdefiniowany
jest tak: a(0)=0, a(1)=1, a(n>1)=a(n-2)+a(n-1). Oznacza to, 偶e ka偶dy element ci膮gu
opr贸cz zerowego i pierwszego jest sum膮 dw贸ch poprzednich (kilka pierwszych
element贸w ci膮gu to: 0, 1, l, 2, 3, 5, 8...). Okna po zape艂nieniu warto艣ciami powin-
ny by膰 albo przewijane, albo czyszczone. W czwartym oknie nale偶y wy艣wietla膰
okr臋gi o losowo wybieranych promieniach. Program powinien si臋 zako艅czy膰, je-
偶eli u偶ytkownik naci艣nie klawisz [Esc].
Oczywi艣cie w pa藕dzierniku 1986 roku taki program dzia艂aj膮cy w systemie DOS
m贸g艂 by膰 jedynie symulacj膮 wielow膮tkow膮 i nikt z uczestnik贸w konkursu nie by艂
wystarczaj膮co mocny - a wi臋kszo艣膰 nie mia艂a po prostu odpowiedniej wiedzy
- aby zakodowa膰 go dla Windows. Ponadto napisanie takiego programu od zera
z pewno艣ci膮 zaj臋艂oby wi臋cej ni偶 p贸艂 godziny!
Wi臋kszo艣膰 uczestnik贸w napisa艂a program, kt贸ry dzieli艂 ekran na cztery cz臋艣ci.
Najwa偶niejszym elementem by艂a p臋tla, kt贸ra sekwencyjnie aktualizowa艂a poszcze-
g贸lne okna i sprawdza艂a, czy nie zosta艂 naci艣ni臋ty klawisz [Esc]. Program zu偶ywa艂
100 procent zasob贸w CPU, co w przypadku aplikacji dosowej by艂o normalne.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1077
Je偶eli program zosta艂by napisany dla Windows 1.0, wynik przypomina艂by mniej
wi臋cej program MULTI1 z rysunku 20-2. Pisz臋 "mniej wi臋cej", poniewa偶 prezen-
towana poni偶ej aplikacja jest przeznaczona dla systemu 32-bitowego. Niemniej
sama struktura i wi臋kszo艣膰 zasadniczego kodu - nie licz膮c definicji funkcji
i zmiennych ani obs艂ugi unikodu - mia艂aby prawdopodobnie w艂a艣nie tak膮 po-
sta膰.
MULTII.C
/*
MULTIl.C - Demo wielowdtkowe
(c) Charles Petzold, 1998
, */
include
include
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int cyChar ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
(
static TCHAR szAppName[] = TEXT ("Multil") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW CS㑇REDRAW ;
;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
, wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
I wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB蘒CONERROR) ;
return 0 ;
)
- hwnd = CreateWindow (szAppName, TEXT ("Multitasking Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
T
g Cz臋艣膰 III: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1077)
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
return msg.wParam ;
)
int CheckBottom (HWND hwnd, int cyClient, int iLine)
(
if (iLine * cyChar + cyChar > cyClient)
(
InvalidateRect (hwnd, NULL, TRUE) ;
UpdateWindow (hwnd) : ,
iLine = 0 ;
)
return iLine ;
)
//
// Okno 1: wy艣wietlanie ciu rosndcych liczb calkowitych
//
LRESULT APIENTRY WndProcl (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static int iNum, iLine, cyClient ;
HDC hdc ;
TCHAR szBufferCl67 ;
switch (message)
( r
case WM_SIZE:
cyClient = HIWORD (lParam) ;
return 0 ; !
case WM_TIMER:
if (iNum < 0)
iNum = 0 ;
iLine = CheckBottom (hwnd, cyClient, iLine) ;
hdc = GetDC (hwnd) ;
TextOut (hdc, 0, iLine * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%d"), iNum++)) ;
ReleaseDC (hwnd, hdc) ;
iLine++ ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ; '
l
//
// Okno 2: wy艣wietlanie ci膮gu rosndcych liczb pierwszych
//
LRESULT APIENTRY WndProc2 (HWND hwnd, UINT messa9e, WPARAM wParam, LPARAM lParam)
,
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1079
static int iNum = 1, iLine, cyClient ;
HDC hdc :
int i, iSqrt ;
TCHAR szBuff ] ;
er[16
s..
switch (message)
f ,.,. ..
case WM_SIZE:
cyClient = HIWORD (lParam) ;
return 0 ;
case WM TIMER:
do (
if (++iNum < 0)
iNum = 0 ;
iSqrt = (int) sqrt (iNum) ;
' i ,:
for (i = 2 ; i <= iSqrt ; i++)
if (iNum % i == 0)
break ;
}
while (i <= iSqrt) ;
iLine = CheckBottom (hwnd, cyClient, iLine) ;
hdc = GetDC (hwnd) ;
i .::;
TextOut (hdc, 0, iLine * cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%d"), iNum)) ;
ReleaseDC (hwnd, hdc) ;
iLine++
return 0,; i
;, :.
I
return DefWindowProc (hwnd, message, wParam, lParam) ;
1
i i ;.r,,
//
// Okno 3: wy艣wietlanie ci膮gu Fibonacciego
//
j :::
LRESULT APIENTRY WndProc3 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static int iNum = 0, iNext = l, iLine, cyClient ;
HDC hdc ;
P: I
int iTem
TCHAR szBuffer[16] ;
i
switch (message) i
(
! case WM_SIZE:
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_TIMER: I
if (iNum < 0)
iNum = 0 ; i
iNext = 1 ;
T
1080 Cz臋艣膰 III: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1079)
iLine = CheckBottom (hwnd, cyClient, iLine) ;
hdc = GetDC (hwnd) ;
TextOut (hdc, 0, iLine * cyChar, szBuffer, '
wsprintf (szBuffer, "%d", iNum)) ;
ReleaseDC (hwnd, hdc) ;
iTemp = iNum ;
iNum = iNext ;
iNext += iTemp ;
iLine++ ;
return 0 ; ,
return DefWindowProc (hwnd, message, wParam, lParam) ;
)
//
// Okno 4: wy艣wietlanie okr臋g贸w o losowych promieniach
//
LRESULT APIENTRY WndProc4 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) '
(
static int cxClient, cyClient ;
HDC hdc ;
int iDiameter ;
switch (message)
f
case WM_SIZE: r
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ; !
case WM_TIMER:
InvalidateRect (hwnd, NULL, TRUE) ;
UpdateWindow (hwnd) ;
iDiameter = rand() % (max (1, min (cxClient, cyClient))) ;
hdc = GetDC (hwnd) ;
Ellipse (hdc, (cxClient - iDiameter) / 2,
(cyClient - iDiameter) / 2,
(cxClient + iDiameter) / 2,
(cyClient + iDiameter) / 2) ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
//
// Gl贸wne okno tworzdce okna potomne
// ,
Rozdziat 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1081
LRESULT APIENTRY WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static HWND hwndChild[4] ;
static TCHAR * szChildClass[] = ( TEXT ("Childl"), TEXT ("Child2"),
TEXT ("Child3"), TEXT ("Child4")
static WNDPROC ChildProc[] = ( WndProcl, WndProc2, WndProc3, WndProc4 ) ;
, HINSTANCE hInstance ;
int i, cxClient, cyClient ;
WNDCLASS wndclass ;
switch (message)
(
case WM_CREATE:
hInstance = (HINSTANCE) GetWindowLong (hwnd, GWL HINSTANCE) ;
wndclass.style = CS HREDRAW CS㑇REDRAW ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = NULL ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ; -
for ( i = 0 ; i < 4 ; i++)
(
wndclass.lpfnWndProc = ChildProc[i] ;
wndclass.lpszClassName = szChildClass[i] ;
RegisterClass (&wndclass) ;
hwndChild[i] = CreateWindow (szChildClass[i], NULL,
WS_CHILDWINDOW WS_BORDER WS㑇ISIBLE,
0, 0, 0, 0.
, 1 hwnd, (HMENU) i, hInstance, NULL) ;
cyChar = HIWORD (GetOialogBaseUnits ()) ;
SetTimer (hwnd, 1, 10, NULL) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
for (i = 0 ; i < 4 ; i++)
MoveWindow (hwndChild[i], (i % 2) * cxClient / 2,
(i > 1) * cyClient / 2,
, cxClient / 2, cyClient / 2, TRUE) ;
return 0 ;
case WM TIMER:
for (i = 0 ; i < 4 ; i++)
SendMessage (hwndChild[i], WM TIMER, wParam, lParam) ;
return 0 ;
case WM﨏HAR:
T
1082 Cz臋艣膰 III: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1081)
if (wParam --- '\x1B')
DestroyWindow (hwnd) ;
return 0 ;
case WM_DESTROY:
KillTimer (hwnd, 1) ;
PostOuitMessage (0) ;
return 0 ;
1
return DefWindowProc (hwnd, message, wParam, lParam> ;
Rysunek 20-2. Program MULTII
Powy偶szy program nie prezentuje niczego, czego by艣my wcze艣niej nie widzieli.
Okno g艂贸wne tworzy cztery okna potomne, z kt贸rych ka偶de zajmuje jedn膮 czwart膮
obszaru roboczego. Ustawia r贸wnie偶 zegar Windows i wysy艂a komunikaty
WM TTMER do ka偶dego z czterech okien potomnych.
Program Windows powiruen przechowywa膰 informacje wystarczaj膮ce do odtwo-
rzenia zawarto艣ci swojego okna podczas komunikatu WM PAINT. MULTI1 tego
nie robi, ale okna s膮 rysowane i usuwane tak szybko, 偶e uzna艂em to za zb臋dne.
Generator liczb pierwszych z WndProc2 nie jest mo偶e zbyt efektywny, ale za to
dzia艂a. Liczb膮 pierwsz膮 jest ka偶da liczba natui'alna wi臋ksza od 1, kt贸ra dzieli si臋
tylko przez 1 i przez sam膮 siebie. Aby sprawdzi膰, czy dana liczba jest liczb膮 pierw-
sz膮, r乪 trzeba dzieli膰 jej przez wszystkie liczby mniejsze od niej - wystarczy przez
pierwszych pierwiastek z n liczb. To w艂a艣nie wyliczanie pierwiastka jest powo-
dem w艂膮czania bibliotek matematycznych w b膮d藕 co b膮d藕 ca艂kowitoliczbowym
programie.
W programie MULTII nie ma nic z艂ego. Korzystarue z zegara Windows jest w艂a-
艣ciwym sposobem symulowania wielow膮tkowo艣ci w przypadku zar贸wno wcze-
艣niejszych wersji Windows, jak i Windows 98. Korzystanie z zegara cz臋sto spo-
walrua jednak program. Je偶eli program jest w stanie aktualizowa膰 wszystkie swoje
okna w czasie jednego komunikatu WM TIMER, i to z zapasem, oznacza to, 偶e
nie wykorzystuje w pe艂ni zasob贸w systemowych.
Ulepszona wersja programu powinna na przyk艂ad ⺻konywa膰 dwie aktualiza-
cje podczas jednego 艂comunikatu WM TIMER. A mo偶e trzy, cztery... kto da wi臋-
cej? Mno偶nik powinien by膰 zale偶ny od pr臋dko艣ci komputera, kt贸ra sk膮din膮d mo偶e
by膰 bardzo r贸偶na. Jednakowo偶 pisanie programu dopasowanego do 25-MHz
komputera 386, 50-MHz komputera 486 albo 100-GHz Pentium 7 nie ma wi臋k-
szego sensu.
Rozwi膮zanie wielow膮tkowe
Przyjrzyjmy si臋 wielow膮tkowemu rozwi膮zaniu zadarua. Na rysunku 20-3 przed-
stawiono je w postaci programu MULTI2.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1083
MULTI2. C '`
*
MULTI2.C - Demo wielowqtkowe
(c) Charles Petzold, 1998
*
,
include
i ncl ude ..:......
i ncl ude ;..,' .
typedef struct
HWND hwnd ;
int cxClient ;
int cyClient ; j,
int cyChar :
BOOL bKill ;
PARAMS, *PPARAMS ;
LRESULT APIENTRY WndProc (HWND, UINT, WPARAM, LPARAM) ; I
, int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow) i
f
static TCHAR szAppName[] = TEXT ("Multi2") ;
HWND hwnd ;
MSG msg ;
,.
WNDCLASS wndclass ;
L:.
wndclass.style = CS_HREDRAW CS-VREDRAW ; ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE BRUSH) ; '
; wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
1 '... :-..
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB-ICONERROR) ;
return 0 ;
)
C
hwnd = CreateWindow (szAppName, TEXT ("Multitasking Demo"), `
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW㑳SEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
3 :;.:::,..
ShowWindow (hwnd, iCmdShow) ;
-
1084 Cz臋艣膰 III: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1083)
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
int CheckBottom (HWND hwnd, int cyClient, int cyChar, int iLine)
(
if (iLine * cyChar + cyChar > cyClient)
(
InvalidateRect (hwnd, NULL, TRUE) ;
UpdateWindow (hwnd) ;
iLine = 0 ;
1
return iLine ;
//
// Okno 1: wy艣wietlanie cidgu rosndcych liczb calkowitych
//
void Threadl (PVOID pvoid)
(
HDC hdc ;
int iNum = 0, iLine = 0 ;
PPARAMS pparams ;
TCHAR szBufferCl6] ;
pparams = (PPARAMS) pvoid ;
while (!pparams->bKill)
(
if (iNum < 0)
iNum = 0 ;
iLine = CheckBottom (pparams->hwnd, pparams->cyClient,
pparams->cyChar, iLine) ;
hdc = GetDC (pparams->hwnd) ;
TextOut (hdc, O, iLine * pparams->cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%d"), iNum++)) ;
ReleaseDC (pparams->hwnd, hdc) ; ,
iLine++ ;
)
endthread () ;
)
LRESULT APIENTRY WndProcl (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static PARAMS params ;
艂
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1085
switch (message)
(
case WM_CREATE:
params.hwnd = hwnd ;
params.cyChar = HIWORD (GetDialogBaseUnits ()) ;
㧏eginthread (Threadl, 0, ¶ms) ;
return 0 ;
case WM_SIZE:
params.cyClient = HIWORD (lParam) ;
return 0 ;
case WM_DESTROY:
params.bkill = TRUE ;
return 0 ;
1
return DefWindowProc (hwnd, message, wParam, lParam) ;
//
// Okno 2: wy艣wietlanie ci膮gu rosn膮cych liczb pierwszych
//
void Thread2 (PVOID pvoid)
(
HDC hdc ;
int iNum = 1, iLine = 0, i, iSqrt ;
PPARAMS pparams ;
TCHAR szBuffer[16] ;
pparams = (PPARAMS) pvoid ;
while (!pparams->bKill)
(
do
(
if (++iNum < 0)
iNum = 0 ;
iSqrt = (int) sqrt (iNum) ;
for (i = 2 ; i <= iSqrt ; i++)
if (iNum % i == 0)
break ;
)
while (i <= iSqrt) ;
iLine = CheckBottom (pparams->hwnd, pparams->cyClient,
pparams->cyChar, iLine) ;
hdc = GetDC (pparams->hwnd) ;
TextOut (hdc, 0, iLine * pparams->cyChar, szBuffer,
wsprintf (szBuffer, TEXT ("%d"), iNum)) ;
ReleaseDC (pparams->hwnd, hdc) ;
iLine++ ;
1086 Cz臋艣膰 III: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1085)
㧐ndthread () ;
)
LRESULT APIENTRY WndProc2 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static PARAMS params ;
r
switch (message)
(
case WM CREATE:
params.hwnd = hwnd ;
params.cyChar = HIWORD (GetDialogBaseUnits ()) ;
㧏eginthread (Thread2, 0, ¶ms) ;
return 0 ;
case WM_SIZE:
params.cyClient = HIWORD (lParam) ;
return 0 ; ,
case WM DESTROY:
params.bKill = TRUE ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
)
//
// Okno 3: wy艣wietlanie ci膮gu Fibonacciego
//
void Thread3 (PVOID pvoid)
I
HDC hdc ;
int iNum = 0, iNext = 1, iLine = 0, iTemp ;
PPARAMS pparams ;
TCHAR szBuffer[16) ;
pparams = (PPARAMS) pvoid ;
while (!pparams->bKill)
if (iNum < 0)
(
iNum = 0 ;
iNext = 1 ;
)
iLine = CheckBottom (pparams->hwnd, pparams->cyClient,
pparams->cyChar, iLine) ;
hdc = GetDC (pparams->hwnd) ;
TextOut (hdc, 0, iLine * pparams->cyChar, szBuffer, '
wsprintf (szBuffer, TEXT ("㩳"), iNum)) ;
ReleaseDC (pparams->hwnd, hdc) ;
iTemp = iNum ;
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1087
iNum = iNext ;
iNext += iTemp : '
iLine++ ;
endthread () ;
LRESULT APIENTRY WndProc3 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
f
static PARAMS params ;
switch (message)
case WM﨏REATE:
params.hwnd = hwnd ;
params.cyChar = HIWORD (GetOialogBaseUnits ()) ;
_beginthread (Thread3, 0, ¶ms) ;
return 0 ;
case WM_SIZE:
params.cyClient = HIWORO (lParam) ;
return 0 ;
case WM﨑ESTROY:
params.bKill = TRUE ;
return 0 ;
return DefWindowProc (hwnd, messa9e, wParam, lParam) ;
l
SI !::
//
// Okno 4: wy艣wietlanie okr臋g贸w o losowych promieniach
//
void Thread4 (PVOID pvoid)
HDC hdc ;
int iDiameter ;
PPARAMS pparams ;
f ::
pparams = (PPARAMS) pvoid ;
while (!PParams->bKill)
(
InvalidateRect (pparams->hwnd, NULL, TRUE) ; '
UpdateWindow (pparams->hwnd) ;
k::;
iDiameter = rand() % (max (i,
min (pparams->cxClient, pparams->cyClient))) ;
f ::.
hdc = GetDC (pparams->hwnd) ;
Ellipse (hdc, (pparams->cxClient - iDiameter) / 2,
` (pparams->cyClient - iDiameter) / 2,
(pparams->cxClient + iDiameter) / 2,
(pparams->cyClient + iDiameter) / 2) ;
ReleaseDC (pparams->hwnd, hdc) ;
1088 Cz臋艣膰 III Zagadnienia zaawansowane
(cigg dalszy ze strony 1087)
㧐ndthread () ;
}
LRESULT APIENTRY WndProc4 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static PARAMS params ;
switch (message)
(
case WM﨏REATE:
params.hwnd = hwnd ;
params.cyChar = HIWORD (GetDialogBaseUnits ()) ;
㧏eginthread (Thread4, 0, ¶ms) ;
return 0 ;
case WM SIZE:
params.cxClient = LOWORD (lParam) ;
params.cy0lient = HIWORD (lParam) ;
return 0 ;
case WM﨑ESTROY:
params.bKill = TRUE ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
//
// Gl贸wne okno tworz膮ce okna potomne
//
LRESULT APIENTRY WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) ,
(
static HWND hwndChildC4] ;
static TCHAR * szChildClassC] = t TEXT ("Childl"), TEXT ("Child2"), ,
TEXT ("Child3"), TEXT ("Child4") } ;
static WNDPROC ChildProc[] = t WndProcl, WndProc2, WndProc3, WndProc4 } ;
HINSTANCE hInstance ;
int i, cxClient, cyClient ;
WNDCLASS wndclass ;
switch (message)
(
case WM_CREATE:
hInstance = (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE) ;
wndclass.style = CS HREDRAW CS_UREDRAW ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = NULL ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE BRUSH) ; r
wndclass:lpszMenuName = NULL ;
for (i = 0 ; i < 4 ; i+艅)
(
wndclass.lpfnWndProc = ChildProcCi] ;
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1089
wndclass.lpszClassName = szChildClassCi] ;
RegisterClass (&wndclass) ;
hwndChildCi] = CreateWindow (szChildClassCi], NULL,
WS_CHILDWINDOW WS BORDER WS㑇ISIBLE,
0, 0, 0, 0,
hwnd, (HMENU) i, hInstance, NULL) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
for (i = 0 ; i < 4 ; i++)
MoveWindow (hwndChildCi], (i % 2) * cxClient / 2,
(i > 1) * cyClient / 2,
cxClient / 2, cyClient / 2, TRUE) ;
return 0 ;
case WM_CHAR:
if (wParam = '\x1B')
DestroyWindow (hwnd) ;
return 0 ;
case WM DESTROY:
PostOuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 20-3. Program MULTI2
Funkcja WinMain i funkcje WndProc z programu MULTI2.C s膮 bardzo podobne
do swoich odpowiednik贸w z MULTIl.C. WndProc rejestruj膮 cztery klasy okien,
tworz膮 okna i zmieniaj膮 ich wymiary podczas komunikatu WM SIZE. Ta wersja
? r贸偶ni si臋 od poprzedruej jedynie tym, 偶e w procedurze WndProc nie jest progra-
mowany zegar ani nie s膮 przetwarzane komunikaty WM⺄IMER.
Istotn膮 r贸偶nic膮 w MULTI2 jest to, 偶e ka偶da z procedur okien potomnych tworzy
inny w膮tek, wywo艂uj膮c podczas komunikatu WM CREATE funkcj臋 beginthread.
Program MULTI2 ma w sumie pi臋膰 dzia艂aj膮cych wsp贸艂bie偶nie w膮tk贸w. W膮tek
g艂贸wny to procedura g艂贸wnego okna, a w膮tki robocze to cztery procedury okien
potomnych. W膮tki robocze u偶ywaj膮 funkcji o nazwach Threadl, Thread2 itd. Te
艂' cztery w膮tki s膮 odpowiedzialne za rysowanie czterech okien.
W kodzie wielow膮tkowyrn z programu RNDRCTMT nie by艂 wykorzystywany
trzeci argument beginthread. Pozwala on w膮tkowi przekazywa膰 do utworzone-
go przeze艅 w膮tku dane za po艣rednictwem 32 bitowych zmiennych. Zazwyczaj
zmienn膮 tak膮 jest wska藕nik do struktury. Pozwala to w膮tkowi tworz膮cemu oraz
utworzonemu korzysta膰 ze wsp贸lnych zmiennych globalnych. Jak wida膰, w pro-
gramie MULTI2 nie ma 偶adnych zmiennych globalnych.
T
1090 Cz臋艣膰 III Zagadnienia zaawansowane
Dla programu MULTI2 zdefiniowa艂em struktur臋 o nazwie PARAMS (na pocz膮t-
ku programu) oraz wska藕nik do tej struktury o nazwie PPARAMS. Struktura ma
pi臋膰 p贸l - uchwyt okna, szeroko艣膰 i wysoko艣膰 okna, szeroko艣膰 i wysoko艣膰 zna-
ku oraz zmienn膮 logiczn膮 o nazwie bKill. Ostatnie pole struktury umo偶liwia w膮t-
kowi tworz膮cemu informowanie utworzonych przeze艅 w膮tk贸w o tym, 偶e nad-
szed艂 czas zako艅czenia pracy.
Przyjrzyjmy si臋 procedurze WndProcl, procedurze okna potomnego, w kt贸rym
wy艣wietlane s膮 rosn膮ce liczby ca艂kowite. Procedura si臋 upro艣ci艂a. Jedyn膮 zmien-
n膮 lokaln膮 jest struktura PARAMS. Podczas obshxgi komunikatu WM_CREATE
procedura okna nadaje warto艣ci polom hwnd i cyChar tej struktury i wywo艂uje
funkcj臋 beginthread, kt贸ra ma utworzy膰 nowy w膮tek u偶ywaj膮cy funkcji Threadl,
przekazuj膮c jej zarazem wska藕nik do struktury. Podczas przetwarzania komuni-
katu WMIZE procedura WndProcl nadaje warto艣膰 polu cyClient struktury, a pod-
czas przetwarzania komunikatu WM DESTROY ustawia pole bKill na TRUE.
Funkcja Thread ko艅czy si臋 wywo艂aniem _endthread. Nie jest to konieczne, ponie-
wa偶 po zako艅czeniu funkcji Thread w膮tek i tak jest niszczony automatycznie.
Funkcja 臋ndthread jest jednak przydatna do ko艅czenia dzia艂ania w膮tku na skr贸-
ty, z g艂臋bi wielu poziom贸w zagnie偶d偶erua kodu.
W艂a艣ciw膮 cz臋艣膰 programu, czyli rysowar乪 w oknie, wykonuje funkcja Threadl,
dzia艂aj膮ca wsp贸艂bie偶nie z pozosta艂ymi w膮tkami. Otrzymuje ona wska藕nik do
struktury PARAMS. Jej g艂贸wne zadanie, przebiegaj膮ce w p臋tli while, polega na
sprawdzaniu w ka偶dym obrocie p臋tli, czy pole bKill ma warto艣膰 TRUE czy FAL-
SE. Je偶eli ma warto艣膰 FALSE, funkcja wykonuje te same operacje co podczas prze-
twarzarua komunikatu WM⺄IMER w MULTIl.C: formatuje liczb臋, pozyskuje
uchwyt do kontekstu urz膮dzenia i wy艣wietla liczb臋 za pomoc膮 TextOut.
Jak zobaczysz, uruchamiaj膮c MULTI2 w Windows 98, okna s膮 aktualizowane
o wiele szybciej ni偶 w MULTIl. Oznacza to, 偶e program bardziej efektywnie wy-
korzystuje moc przetwarzania procesora. Jest jeszcze jedna r贸偶nica mi臋dzy MUL-
TIl i MULTI2: na og贸艂, kiedy przesuwa si臋 lub wymiaruje okno, domy艣lna proce-
dura okna wchodzi do p臋tli modalnej i wy艣wietlanie w oknach si臋 ko艅czy. W pro-
gramie MULTI2 wy艣wietlanie si臋 nie ko艅czy.
Jakie艣 problemy?
Mo偶e si臋 wydawa膰, 偶e MULTI2 nie jest tak kuloodporny, jak m贸g艂by by膰. Aby
zrozumie膰, do czego zmierzam, przyjrzyjmy si臋 ruekt贸rym "usterkom" progra-
mu MULTI2.C. Rozpatrzymy je na przyk艂adzie funkcji WndProcl i Threadl.
WndProcl dzia艂a jako w膮tek g艂贸wny MULTI2, a Threadl jako roboczy. W chwi-
lach, kiedy Windows prze艂膮cza si臋 mi臋dzy nimi, w膮tki s膮 zmienne i nieprzewi-
dywalne. Za艂贸偶my, 偶e dzia艂a Threadl i 偶e wykona艂 si臋 w艂a艣nie jego kod spraw-
dzaj膮cy, czy pole bKill struktury PARAMS ma w"rto艣膰 TRUE. Nie ma, ale potem
Windows prze艂膮cza sterowanie do w膮tku g艂贸wnego, w kt贸rym u偶ytkownik ko艅- '
czy program. WndProcl otrzymuje komunikat WM﨑ESTROY i nadaje argumen-
towi bKill warto艣膰 TRUE. 艁ups! Za p贸藕no! Nagle system operacyjny prze艂膮cza si臋
na Threadl i funkcja pr贸buje pozyska膰 uchwyt kontekstu urz膮dzenia do nieist-
niej膮cego okna.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1091
Okazuje si臋, 偶e to nie problem. Sam Window 98 jest na tyle odporny, 偶e funkcje
graficzne po prostu ko艅cz膮 si臋 niepowodzeniem, nie powoduj膮c powa偶niejszych
konsekwencji.
Poprawne programowanie wielow膮tkowe opiera si臋 na technikach synchroniza-
cji w膮tk贸w (a szczeg贸lnie na sekcjach krytycznych), kt贸re om贸wi臋 szczeg贸艂owo
ju偶 zaraz. Sekcje krytyczne s膮 to fragmenty kodu rozpoczynaj膮ce si臋 wywo艂aniem
EnterCriticalSection i ko艅cz膮ce si臋 wywo艂aniem LeaveCriticalSection. Je偶eli jeden
z w膮tk贸w wejdzie do sekcji krytycznej, inny nie mo偶e ju偶 tego zrobi膰. Drugi w膮-
tek jest zablokowany na funkcji EnterCriticalSection, co trwa do czasu wywo艂ania
przez pierwszy funkcji LeaveCriticalSection.
Innym problemem z MLTLTI2 jest to, 偶e w czasie, gdy w膮tek roboczy generuje dane
wyj艣ciowe (rysuje), w膮tek g艂贸wny mo偶e odebra膰 komunikat WM﨓RASEBKGND
lub WM⺁AINT. Stosuj膮c sekcj臋 krytyczn膮, mo偶na zapobiec sytuacji, w kt贸rej
w tym samym oknie mia艂yby rysowa膰 jednocze艣nie dwa w膮tki. Ale eksperymen-
ty pokazuj膮, 偶e Windows 98 szereguje poprawnie dost臋p do funkcji graficznych.
Oznacza to, 偶e jeden w膮tek nie mo偶e rysowa膰 w oknie, dop贸ki drugi nie przesta-
nie.
W dokumentacji Windows 98 mo偶na si臋 natkn膮膰 na ostrze偶enia informuj膮ce o jed-
nym obszarze, w kt贸rym funkcje graficzne nie s膮 szeregowane, ale dotyczy to
korzystania z obiekt贸w GDI, czyli na przyk艂ad pi贸r, p臋dzli, czcionek, bitmap, re-
gion贸w i palet. Mo偶liwe jest, 偶e jeden w膮tek zniszczy obiekt u偶ywany przez inny.
Rozwi膮zanie tego problemu wymaga zastosowania sekcji krytycznej albo jeszcze
lepiej - niewsp贸艂u偶ytkowania obiekt贸w GDI przez w膮tki.
Zalety snu
Om贸wi艂em, co uwa偶am za najlepsz膮 architektur臋 dla programu wielow膮tkowe-
go: w膮tek g艂贸wny powinien tworzy膰 wszystkie okna programu, zawiera膰 wszyst-
kie procedury okien i przetwarza膰 wszystkie przesy艂ane do nich komunikaty.
W膮tki robocze powinny za艣 zajmowa膰 si臋 g艂贸wnym przetwarzaniem i bra膰 na
siebie najbardziej czasoch艂onne i najci臋偶sze zadania.
Za艂贸偶my jednak, 偶e chcemy w w膮tku roboczym zrealizowa膰 animacj臋. W Win-
dows animacje tworzy si臋 na og贸艂 za pomoc膮 komunikat贸w WM⺄IMER. Ale je偶eli
w膮tek roboczy nie tworzy okna, nie mo偶e r贸wnie偶 otrzymywa膰 tych komunika-
t贸w. Animacja pozbawiona sygna艂贸w zegarowych b臋dzie jednak prawdopodob-
nie za szybka.
Rozwi膮zaniem jest funkcja Sleep. W efekcie w膮tek wywo艂uje funkcj臋 Sleep w celu
dobrowolnego zawieszenia swojego dzia艂ania. Jedynym argumentem tej funkcji
jest czas nieaktywno艣ci (snu) liczony w milisekundach. Funkcja Sleep nie ko艅czy
si臋, dop贸ki nie minie zadany czas. W tyxn czasie w膮tek jest zawieszony i nie otrzy-
muje przydzia艂owych kwant贸w czasu procesora (cho膰 oczywiste jest, 偶e wyma-
ga ma艂ej ilo艣ci czasu przetwarzania podczas tykru臋膰 zegara, gdy system spraw-
dza, czy w膮tek powinien ju偶 zosta膰 odwieszony). Funkcja Sleep z argumentem 0
powoduje oddanie przez w膮tek pozosta艂ej cz臋艣ci jego kwantu czasu.
Zawieszany jest tylko ten w膮tek, w kt贸rym wywo艂ana zosta艂a funkcja Sleep. Sys-
: tem wci膮偶 zapewnia dzia艂anie pozosta艂ym w膮tkom, zar贸wno tego samego pro-
1092 Cz臋艣膰 III Zagadnienia zaawansowane
cesu, jak i innych. Funkcji Sleep u偶y艂em w programie SCRAMBLE z rozdzia艂u 14.
Za jej pomoc膮 spowolni艂em operacj臋 mieszania zawarto艣ci graficznej ekranu.
Funkcji Sleep nie umieszcza si臋 z regu艂y w w膮tku g艂贸wnym, poniewa偶 zwalnia to
przetwarzanie. W programie SCRAMBLE nie by艂y tworzone 偶adne okna, wi臋c
taki problem nie istnieje.
Synchronizacja w膮tk贸w
Mniej wi臋cej raz do roku przestaj膮 dzia艂a膰 艣wiat艂a na ruchliwym skrzy偶owaniu
za moim oknem. Skutkiem jest chaos. Mimo i偶 samochody z regu艂y unikaj膮 zde-
rze艅, cz臋sto niewiele do tego brakuje.
Skrzy偶owanie dw贸ch dr贸g mo偶emy nazwa膰 sekcj膮 krytyczn膮. Samoch贸d nad-
je偶d偶aj膮cy z pohxdnia i samoch贸d nadje偶d偶aj膮cy z zachodu nie mog膮 jednocze-
艣nie przeci膮膰 skrzy偶owania bezkolizyjnie. W zale偶no艣ci od nat臋偶enia ruchu sto-
suje si臋 r贸偶ne rozwi膮zania tego problemu. W przypadku zastosowania 艣wiate艂
na skrzy偶owaniu o du偶ej widoczno艣ci mo偶na zaufa膰 kierowcom, 偶e b臋d膮 si臋 pra-
wid艂owo w艂膮czali do ruchu. Bardziej nasilony ruch mo偶e wymaga膰 stosowania
znaku stopu, a przy jeszcze wi臋kszym niezb臋dne jest stosowanie 艣wiate艂. 艢wiat艂a
s艂u偶膮 do organizowania pracy skrzy偶owania (o ile oczywi艣cie dzia艂aj膮).
Sekcja krytyczna
Tradycyjne programy komputerowe w systemie ednozadaniowym nie wymaga-
j膮 "艣wiate艂", kt贸re koordynowa艂yby ich prac臋. Dzia艂aj膮 tak, jakby nale偶a艂a do nich
ca艂a droga. Nie istnieje nic, z czym mog艂aby nast膮pi膰 kolizja.
Nawet w systemie wielozadaniowym wi臋kszo艣膰 program贸w dzia艂a rzekomo nie-
zale偶nie od siebie. Niemniej pewne trudno艣ci mog膮 wyst臋powa膰. Oba programy
mog膮 na przyk艂ad podj膮膰 pr贸b臋 odczytania i zapisania danych do jednego pliku
jednocze艣nie. W takich przypadkach system operacyjny zapewnia wsp贸艂u偶ytko-
wanie plik贸w oraz mechanizm blokowania rekord贸w.
W systemie operacyjnym obs艂uguj膮cym wielow膮tkowo艣膰 sytuacja si臋 kompliku-
je i staje si臋 potencjalnie niebezpieczna. Nierzadko dwa lub kilka w膮tk贸w korzy-
sta z tych samych danych. Jeden w膮tek mo偶e na przyk艂ad modyfikowa膰 jakie艣
zmienne, a inny z nich korzysta膰. Czasem staje si臋 to 藕r贸d艂em problem贸w, a cza-
sem nie. Nale偶y pami臋ta膰, 偶e system operacyjny mo偶e prze艂膮cza膰 sterowanie z jed-
nego w膮tku na inny tylko mi臋dzy instrukcjami kodu maszynowego. Je偶eli
wsp贸艂u偶ytkowana przez w膮tki jest tylko jedna liczba ca艂kowita, w贸wczas mody-
fikacje tej zmiennej s膮 z regu艂y realizowane pojedynczymi instrukcjami maszy-
nowymi, a wi臋c potencjalne b艂臋dy s膮 raczej wykluczone.
Za艂贸偶my jednak, 偶e w膮tki u偶ywaj膮 kilku wsp贸lnych zmiennych ca艂kowitych lub
te偶 ca艂ych struktur danych. Cz臋sto konieczne jest zachowanie wewn臋trznej sp贸j-
no艣ci mi臋dzy polami struktury albo mi臋dzy ca艂ymi zmiennymi. System opera-
cyjny m贸g艂by przerwa膰 w膮tek w po艂owie aktualizacji tych zmiennych. W贸wczas
w膮tek odczytuj膮cy owe dane stan膮艂by w obliczu niesp贸jno艣ci.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1093
Skutkiem tego by艂aby kolizja; nietrudno sobie wyobrazi膰, jak podobny b艂膮d m贸g艂-
by zniszczy膰 program. Aby temu zapobiec, potrzebny jest programistyczny od-
powiednik 艣wiate艂 koordynuj膮cych i synchronizuj膮cych ruch na skrzy偶owaniach
(tutaj: prac臋 w膮tk贸w). Jest nim sekcja krytyczna: blok kodu, kt贸rego nie mo偶na
przerwa膰.
Sekcje krytyczne pe艂ni膮 cztery funkcje. Aby z nich skorzysta膰, trzeba najpierw
zdefiniowa膰 obiekt sekcji krytycznej, kt贸rym jest zmienna globalna typu CRITI-
CAL SECTION. Oto przyk艂adowa deklaracja:
CRITICALECTION cs;
脫w typ danych o nazwie CRITICAL SECTION to struktura, kt贸rej pola s膮 u偶y-
wane tylko wewn臋trznie przez Windows. Obiekt sekcji krytycznej musi zosta膰
najpierw zainicjowany przez jeden z w膮tk贸w programu za pomoc膮 wywo艂ania:
InitializeCriticalSection (&cs);
Powy偶sza instrukcja powoduje utworzenie obiektu sekcji krytycznej o nazwie cs.
Dokumentacja elektroniczna tej funkcji zawiera nast臋puj膮ce ostrze偶enia: "Obiek-
tu sekcji krytycznej nie mo偶na ani przenosi膰, ani kopiowa膰. Proces nie mo偶e r贸w-
nie偶 modyfikowa膰 obiektu i powinien go traktowa膰 jako logiczn膮 czarn膮 skrzyn-
k臋". Mo偶na to sformu艂owa膰 inaczej: "Nie kombinuj nic z tym obiektem, nawet na
niego nie patrz".
Kiedy obiekt sekcji krytycznej zostanie ju偶 zainicjowany, w膮tek mo偶e wej艣膰 w sek-
cj臋 krytyczn膮 za pomoc膮 wywo艂ania:
EnterCriticalSection (&cs);
Teraz o w膮tku mo偶na powiedzie膰, 偶e "posiad艂" obiekt sekcji krytycznej. Obiektu tego
nie mog膮 posiada膰 偶adne dwa w膮tki jednocze艣nie. Je偶eli wi臋c jaki艣 inny w膮tek wej-
dzie w sekcj臋 krytyczn膮, nast臋pny w膮tek, kt贸ry wywo艂a EnterCriticalSection z obiek-
tem tej samej sekcji, zostanie na tym wywo艂aniu zawieszony. Funkcja sko艅czy dzia-
艂anie dopiero wtedy, gdy pierwszy w膮tek opu艣ci sekcj臋 krytyczn膮, wywo艂uj膮c:
LeaveCriticalSection (&cs);
Teraz sekcj臋 krytyczn膮b臋dzie posiada艂 drugi w膮tek-zawieszony dot膮d na swoim
wywo艂aniu EnterCriticalSection. Sko艅czy si臋 r贸wnie偶 wywo艂anie funkcji, co po-
zwoli w膮tkowi kontynuowa膰 dzia艂anie.
Kiedy obiekt sekcji krytycznej nie jest ju偶 wi臋cej potrzebny programowi, mo偶na
go usun膮膰 wywo艂aniem:
DeleteCriticalSection (&cs);
Zwalnia ono wszelkie zasoby systemowe, kt贸re zosta艂y zarezerwowane na po-
trzeby obiektu sekcji.
Om贸wiony tu mechanizm sekcji krytycznej wi膮偶e si臋 z poj臋ciem wzajemnego
wykluczania, kt贸re pojawi si臋 ponownie podczas dalszego zg艂臋biania tajemnic
synchronizacji w膮tk贸w. W danej chwili tylko jeden w膮tek mo偶e posiada膰 dan膮
sekcj臋 krytyczn膮. Tak wi臋c jeden w膮tek mo偶e wej艣膰 do sekcji krytycznej, ustawi膰
pola struktury i opu艣ci膰 sekcj臋. Potem kolejny w膮tek korzystaj膮cy ze struktury
mo偶e r贸wnie偶 wej艣膰 do sekcji krytycznej, zrealizowa膰 dost臋p do p贸l struktury
i opu艣ci膰 sekcj臋.
1094 Cz臋艣膰 III: Zagadnienia zaawansowane
Zauwa偶my, 偶e mo偶na definiowa膰 wiele obiekt贸w sekcji krytycznych - na przy-
k艂ad cs1 i cs2. Je偶eli jaki艣 program ma cztery w膮tki, z kt贸rych dwa pierwsze u偶y-
waj膮 wsp贸lnych danych, jeden obiekt sekcji b臋dzie dla nich; je偶eli ponadto drugie
dwa w膮tki wsp贸艂u偶ytkuj膮 jakie艣 dane, one te偶 otrzymaj膮 jeden (inny) obiekt sekcji.
Nale偶y tak偶e pami臋ta膰 o zachowaniu ostro偶no艣ci podczas wykorzystywania sekcji
krytycznych w w膮tku g艂贸wnym. Je偶eli bowiem w膮tek roboczy b臋dzie przetrzy-
mywa艂 sekcj臋 krytyczn膮, mo偶e zawiesi膰 g艂贸wny w膮tek na d艂ugi czas. W w膮tkach
roboczych sekcje krytyczne stosuje si臋 na og贸艂 do kopiowania p贸l struktur do
zmiennych lokalnych.
Jednym z ogranicze艅 sekcji krytycznych jest to, 偶e mog膮 by膰 one u偶ywane do
koordynowania w膮tk贸w tylko jednego procesu. S膮 na przyk艂ad sytuacje, w kt贸-
rych niezb臋dne jest koordynowanie w膮tk贸w z dw贸ch r贸偶nych proces贸w u偶ywa-
j膮cych wsp贸lnego zasobu (na przyk艂ad pami臋ci wsp贸lnej). Do tego celu nie mo偶-
na zastosowa膰 sekcji krytycznych. S艂u偶膮 do tego obiekty nazywane do艣膰 dziwnie
"
muteksami". Nazwa ta jest zlepkiem angielskiego okre艣lenia wzajemnego wy-
kluczania (mutex od mutual exclusion). Rozwini臋cie to m贸wi dok艂adnie, o co cho-
dzi. Chodzi o to, aby zabezpieczy膰 w膮tki programu przed przerwaniem w chwili
aktualizowania lub u偶ywania pami臋ci wsp贸lnej czy innych zasob贸w.
Sygnalizowanie zdarze艅
Najcz臋stszym zastosowaniem wielow膮tkowo艣ci w programach jest realizowanie
pewnych czasoch艂onnych zada艅 przetwarzania. Mo偶emy je nazywa膰 powa偶nymi
zadaniami - pod t膮 nazw膮 kryje si臋 wszystko, co dany program ma do wykonania
w czasie d艂u偶szym ni偶 jedna dziesi膮ta sekundy. Do typowych powa偶nych zada艅
zalicza si臋: sprawdzanie pisowni w procesorze tekst贸w, sortowanie lub indekso-
wanie plik贸w baz danych, od艣wie偶anie zawarto艣ci arkuszy kalkulacyjnych, dru-
kowanie, a nawet bardziej z艂o偶one rysowania. Oczywi艣cie, najlepszym sposobem
na spe艂nianie regu艂y jednej dziesi膮tej sekundy, jak ju偶 pisa艂em, jest delegowanie
powa偶nych zada艅 w膮tkom roboczym. W膮tki te nie tworz膮 okien, wi臋c nie obowi膮-
zuje ich regu艂a.
Czasem dobrze jest, kiedy w膮tek roboczy mo偶e poinformowa膰 w膮tek g艂贸wny o za-
ko艅czeniu swojej pracy albo kiedy w膮tek g艂贸wny mo偶e przerwa膰 dzia艂anie w膮t-
ku roboczego. Tym w艂a艣nie zajmiemy si臋 obecnie.
Program BIGJOB1
Jako symulacji powa偶nego zadarua u偶yj臋 ci膮gu kalkulacji zmiennoprzecinkowych
,
nazywanych czasem "dzikim testem". Wyliczenia obejmuj膮 inkrementacj臋 liczby
ca艂kowitej realizowan膮 przez: podnoszenie jej do kwadratu i wyci膮gni臋cie z wyni-
ku pierwiastka (znosz膮cego pot臋gowanie), zastosowanie funkcji log i exp (kt贸re te偶
si臋 znosz膮) oraz funkcji atan i tan (podobnie), a na ko艅cu dodanie do wyniku 1.
Program BIGJOBI przedstawiono na rysunku 20-4.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1095
BIGJOBI.C
/*
BIGJOBI.C - Demo wielowdtkowe
(c) Charles Petzold, 1998
*/
C..::
, include
include
include
define REP 1000000
define STATUS_READY 0
define STATUS_WORKING 1
define STATUS DONE 2
define WM CALC DONE (WM_USER + 0)
define WM CALC ABORTED (WM USER + 1)
i
typedef struct !.,,.
(
HWND hwnd ;
BOOL bContinue ;
)
PARAMS, *PPARAMS :
' LRESULT APIENTRY WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
I PSTR szCmdLine, int iCmdShow)
static TCHAR szAppNameC] = TEXT ("BigJobl") ;
HWND hwnd ; ,.,
MSG msg : ''.:.':.
i i:.
WNDCLASS wndclass ;
f. :::: .
' wndclass.style = CS_HREDRAW CS VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
j wndclass.cbClsExtra = 0 ;
I wndclass.cbWndExtra = 0 ; '
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; i,.:
i wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ;
; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE﨎RUSH) :
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ; !
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB-ICONERROR) ;
return 0 ;
)
i >
hwnd = CreateWindow (szAppName, TEXT ("Multithreading Demo"), f,,
WS﨩VERLAPPEDWINDOW,
1096 Cz臋艣膰 Iil: Zagadnienia zaawansowane
(ci膮g dalszy ze strony 1095)
CW㑳SEDEFAULT, CW㑳SEDEFAULT,
CW_USEDEFAULT, CW USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
)
!
void Thread (PVOID pvoid)
(
double A = 1.0 ;
INT i ;
LONG lTime ;
volatile PPARAMS pparams ;
pparams = (PPARAMS) pvoid ;
lTime = GetCurrentTime () ;
for (i = 0 ; i < REP && pparams->bContinue ; i++)
A = tan (atan (exp (log (sqrt (A * A))))) + 1.0 ;
if (i == REP)
(
lTime = GetCurrentTime () - lTime ;
SendMessage (pparams->hwnd, WM CALC DONE, 0, lTime) ;
else
SendMessage (pparams->hwnd, WM CALC ABORTED, 0, 0) ;
㧐ndthread () ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static INT iStatus ;
static LONG lTime ;
static PARAMS params ;
static TCHAR * szMessage[] = ( TEXT ("Ready (left mouse button begins)"),
TEXT ("Working (right mouse button ends)"),
TEXT ("%d repetitions in %ld msec") ) ;
HDC hdc ;
PAINTSTRUCT ps ; '
RECT rect ; !
TCHAR szBuffer[64] ;
switch (message)
(
case WM_LBUTTONDOWN:
if (iStatus == STATUS⺈ORKING)
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1097
i
MessageBeep (0) ;
; return 0 ;
iStatus = STATUS WORKING ;
I params.hwnd = hwnd ;
params.bContinue = TRUE ;
; 㧏eginthread (Thread, 0, ¶ms) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_RBUTTONDOWN:
params.bContinue = FALSE ;
return 0 ;
i
case WM CALC DONE:
I
lTime = lParam ;
iStatus = STATUS_DONE ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_CALC_ABORTED:
iStatus = STATUS_READY ;
InvalidateRect (hwnd, NULL, TRUE) ; !
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
i. '; ;
GetClientRect (hwnd, &rect) ;
蘒'.:.
wsprintf (szBuffer, szMessageCiStatus], REP, lTime) ; i`
DrawText (hdc, szBuffer, -1, &rect,
DT SINGLELINE DT CENTER DT UCENTER) ;
EndPaint (hwnd, &ps) ;
`
return 0 ; ii..
case WM_DESTROY: I..':..
PostOuitMessage (0) ;
return 0 ;
returp DefWindowProc (hwnd, message, wParam, lParam) ;
' Rysunek 20-4. Program BIGJOB1
;
Jest to do艣膰 prosty program, ale my艣l臋, 偶e wystarczy do zilustrowania og贸lnego
sposobu realizowania powa偶nych zada艅 w programach wielow膮tkowych. Aby
u偶y膰 programu BIGJOBl, kliknij lewym przyciskiem myszy obszar roboczy jego
okna. Rozpocznie to cykl 1 000 000 powt贸rze艅 "dzikich wylicze艅". W kompute-
rze z procesorem Pentium II 300 MHz zajmuje to oko艂o 2 sekund. Po zako艅cze-
1098 Cz臋艣膰 Iil: Zagadnienia zaawansowane
niu wylicze艅 w oknie wy艣wietlany jest czas, jaki up艂yn膮艂. Klikni臋cie prawym przy-
ciskiem myszy obszaru roboczego podczas trwania wylicze艅 umo偶liwia przerwa-
nie programu.
Przyjrzyjmy si臋 wi臋c, jak jest to zrealizowane:
Procedura okna zawiera zmienn膮 statyczn膮 o nazwie iStatus (kt贸rej mo偶na nada膰
warto艣膰 jednej z trzech sta艂ych zdefiniowanych na pocz膮tku programu, rozpo-
czynaj膮cych si臋 prefiksem STATUS), informuj膮c膮, czy program jest gotowy do
przeprowadzenia wylicze艅, przeprowadza wyliczenia czy ju偶 je zako艅czy艂. Pro-
gram u偶ywa zmiennej iStatus podczas obs艂ugi komunikatu WM⺁AINT do wy-
艣wietlania odpowiedniego 艂a艅cucha znak贸w na 艣rodku obszaru roboczego.
Procedura okna zawiera ponadto struktur臋 statyczn膮 (typu PARAMS, tak偶e zde-
finiowan膮 na pocz膮tku programu) do wymiany danych mi臋dzy procedur膮 okna
i w膮tkiem roboczym. Struktura ma tylko dwa pola: hwnd (uchwyt do okna pro-
gramu) i bContinue (zmienna logiczna informuj膮ca w膮tek, czy ma kontynuowa膰
wyliczenia czy nie).
Klikni臋cie obszaru roboczego lewym przyciskiem myszy powoduje nadanie
zmiennej iStatus i dw贸m polom struktury PARAMS warto艣ci STATUS WORKING.
Pole hwnd tej struktury jest ustawiane na uchwyt okna, a pole bContinue na TRUE.
Procedura okna wywo艂uje funkcj臋 beginthread. Funkcja w膮tku roboczego Thread
zaczyna si臋 od wywo艂ania funkcji GetCurrentTime w celu odczytania, ile czasu
(w milisekundach) up艂yn臋艂o od momentu uruchomienia Windows. Nast臋pnie wy-
konywana jest p臋tla for, w kt贸rej 1 000 000 razy przeprowadzane s膮 "dzikie wyli-
czenia . Jak mo偶na zauwa偶y膰, w膮tek wypadnie z p臋tli, je偶eli bContinue osi膮gnie
warto艣膰 FALSE.
Po p臋tli for funkcja Thread sprawdza, czy rzeczywi艣cie przeprowadzi艂a 1 000 000
iteracji wylicze艅. Je偶eli tak, wywo艂uje ponownie funkcj臋 GetCurrentTime, po czym
za pomoc膮 SendMessage wysy艂a do procedury okna zdefiniowany na potrzeby tego
programu komunikat WM㑳SER DONE, podaj膮c w nim jako lParam czas trwa-
nia wylicze艅. Je偶eli wyliczenia sko艅czy艂yby si臋 przedwcze艣nie (czyli je偶eli pole
bContinue struktury PARAMS w trakcie trwania p臋tli osi膮gn臋艂oby warto艣膰 FAL-
SE), w膮tek wys艂a艂by do procedury okna komunikat WM USER ABORTED. W膮-
tek ko艅czy poprawnie swoje dzia艂anie za pomoc膮 wywo艂ania 臋ndthread.
W procedurze okna, kiedy obszar roboczy zostanie klilali臋ty prawym przyciskiem
myszy, pole bContinue struktury PARAMS otrzymuje warto艣膰 FALSE. W ten oto spo-
s贸b mo偶na przerwa膰 wyliczenia, zanim p臋tla w膮tku dojdzie samoczynnie do ko艅ca.
Zauwa偶my, 偶e zmienna pparams w procedurze Thread jest zdefiniowana jako ulotna
(ang. volatile). Kwalifikator typu informuje kompilator, 偶e zmienna mo偶e by膰 mo-
dyfikowana inaczej, ni偶 wynika艂oby to z instrukcji programu (powiedzmy przez
inny w膮tek). Jest to informacja dla kompilatora, 偶e nie powinien czyni膰 (w ra-
mach procedur optymalizacyjnych) 偶adnych za艂o偶e艅 co do warto艣ci wyra偶enia
pparams->bContinue. Optymalizacja polega na przyk艂ad na tym, 偶e przez ca艂y czas
trwania p臋tli for wyra偶enie to mog艂oby mie膰 niezmienn膮 warto艣膰, a wi臋c nie ma
sensu jej sprawdza膰 w ka偶dej iteracji p臋tli. Tak wi臋c s艂owo kluczowe volatile jest
niezb臋dne, gdy偶 wy艂膮cza takie optymalizacje w odniesieniu do definiowanej
zmiennej.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1099
Procedura okna zaczyna przetwarzanie komunikatu WM㑳SER DONE od za-
pisania znacznika czasu. Zar贸wno przetwarzanie komunikatu WM LTSER DO-
NE, jak i komunikatu WM USER ABORTED obejmuje w dalszej cz臋艣ci wywo艂a-
nie funkcji InvalidateRect w celu wygenerowania komunikatu WM PAINT i wy-
艣wietlenia nowego 艂a艅cucha znak贸w w obszarze roboczym.
Zawsze warto przewidzie膰 w programie tak膮 zmienn膮 logiczn膮 jak bContinue, aby
w膮tek m贸g艂 zako艅czy膰 si臋 poprawnie. Funkcji KiIlThread nale偶y u偶ywa膰 tylko
wtedy, gdy poprawne ko艅czenie jest skomplikowane, poniewa偶 w膮tki mog膮 re-
zerwowa膰 w systemie zasoby, na przyk艂ad pami臋膰. Je偶eli zarezerwowany przez
w膮tek blok pami臋ci rue zostanie zwolniony w chwili ko艅czenia w膮tku, pozosta-
nie wci膮偶 zarezerwowany, a wi臋c bezu偶yteczny. W膮tki to nie procesy: rezerwo-
wane przez nie zasoby s膮 wsp贸lne dla wszystkich w膮tk贸w procesu, dlatego te偶
nie s膮 automatycznie zwalniane z chwil膮 ko艅czenia w膮tku. Dobra praktyka pro-
gramistyczna nakazuje, aby ka偶dy w膮tek zwalnia艂 wszystkie zasoby, kt贸re sam
zarezerwuje.
Zauwa偶my, 偶e podczas gdy dzia艂a jeszcze drugi w膮tek, mo偶e zosta膰 utworzony
trzeci, na przyk艂ad je偶eli Windows prze艂膮czy sterowanie z drugiego w膮tku na
pierwszy mi臋dzy wywo艂aniem SendMessage i 臋ndthread, a procedura okna utwo-
rzy nowy w膮tek w odpowiedzi na klikni臋cie mysz膮. Akurat w naszym przyk艂a-
dzie nie stanowi to problemu, ale gdyby zdarzy艂o si臋 w powa偶nej aplikacji, nale-
偶a艂oby zastosowa膰 sekcj臋 krytyczn膮 eliminuj膮c膮 kolizj臋 w膮tk贸w.
Obiekt zdarzenia
W programie BIGJOB1 nowy w膮tek jest tworzony za ka偶dym razem, kiedy po-
trzebne jest przeprowadzenie wylicze艅; w膮tek ko艅czy dzia艂anie wraz z uko艅cze-
niem wylicze艅.
Rozwi膮zanie alternatywne to utrzymywa膰 przez ca艂y czas trwania programu
aktywny w膮tek i w艂膮cza膰 w nim obliczenia tylko wtedy, gdy trzeba. jest to ideal-
ne zastosowanie dla obiektu zdarzenia.
Obiekt zdarzenia jest albo zasygnalizowany (inaczej: ustawiony), albo niezasy-
gnalizowany (inaczej: zresetowany). Tworzy si臋 go wywo艂aniem:
hEvent = CreateEvent (&sa. fManual. fInitial, pszName);
Pierwszy argument (wska藕nik do struktury SECURITY嗀TTRIBUTES) i argument
ostatni (nazwa obiektu zdarzenia) s膮 uwzgl臋dniane tylko wtedy, gdy obiekty
zdarze艅 s膮 wsp贸艂u偶ytkowane przez procesy. W jednym procesie argumenty te
s膮 z regu艂y ustawiane na NULL. Argumentowi flnitial nale偶y nada膰 warto艣膰 TRUE,
je偶eli obiekt zdarzenia ma by膰 pocz膮tkowo zasygnalizowany, a warto艣膰 FALSE,
je偶eli ma by膰 pocz膮tkowo niezasygnalizowany. Teraz pokr贸tce zajm臋 si臋 argu-
mentem fManual.
Aby zasygnalizowa膰 istniej膮cy obiekt zdarzenia, nale偶y wywo艂a膰:
SetEvent (hEvent);
Aby zresetowa膰 (odsygnalizowa膰) istniej膮cy obiekt zdarzenia, nale偶y wywo艂a膰:
ResetEvent (hEvent);
T
i
1100 Cz臋艣膰 III: Zagadnienia zaawansowane I
W programie funkcj臋:
WaitForSingle0bject (hEvent, dwTimeOut);
wywo艂uje si臋 zazwyczaj z drugim argumentem r贸wnym INFINITE. Funkcja ta
wraca natychmiast, je偶eli obiekt zdarzenia jest ju偶 ustawiony (zasygnalizowany).
W przeciwnym razie zawiesza dzia艂anie w膮tku do czasu, gdy jego status zmieni
si臋 na zasygnalizowany. Drugiemu argumentowi mo偶na nada膰 warto艣膰 limitu
czasu w milisekundach, aby funkcja ko艅czy艂a dzia艂anie, je偶eli w ci膮gu zadanego
czasu obiekt nie zmieni si臋 w zasygnalizowany.
Je偶eli argument fManual pierwszego wywo艂ania CreateEvent b臋dzie r贸wny FAL-
SE, obiekt zdarzenia stanie si臋 automatycznie zasygnalizowany po zako艅czeniu
funkcji Wai#ForSingleObject. Ta w艂a艣ciwo艣膰 sprawia, 偶e funkcja ResetEvent staje si臋
zb臋dna.
Teraz jeste艣my odpowiednio przygotowani na spotkanie z programem BIGJOB2.C.
Przedstawiono go na rysunku 20-5.
BIGJOB2.C
/*
BIGJOB2.C - Demo wielow⺶kowe
(c) Charles Petzold, 1998
*/
i㱮nclude
i㱮nclude
itinclude
ildefine REP 1000000
i㩳efine STATUS_READY 0
ildefine STATUS_WORKING 1
lldefine STATUS-DONE 2
define WM_CALC_DONE (WM_USER + 0)
define WM CALC ABORTED ,(WM USER + 1)
typedef struct
(
HWND hwnd ;
HANDLE hEvent ;
BOOL bContinue ;
)
PARAMS, *PPARAMS ;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[7 = TEXT ("BigJob2")
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass :
wndclass.style = CS﨟REDRAW CS㑇REDRAW ;
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1 101
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
. wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB ICONERROR) ;
hwnd = CreateWindow (szAppName, TEXT ("Multithreading Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW㑳SEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
)
return msg.wParam ;
)
void Thread (PVOID pvoid)
(
double A = 1.0 ;
INT i ;
LONG lTime ;
volatile PPARAMS pparams ;
pparams = (PPARAMS) pvoid ;
while (TRUE)
(
WaitForSingle0bject (pparams->hEvent, INFINITE) ;
lTime = GetCurrentTime () ;
for (i = 0 ; i < REP && pparams->bContinue ; i++)
A = tan (atan (exp (1og (sqrt (A * A))))) + 1.0 ;
if (i REP)
(
lTime = GetCurrentTime () - lTime ;
PostMessage (pparams->hwnd, WM CALC DONE, 0, lTime) ;
1102 Cz臋艣膰 III: Zagadnienia zaawansowane
(
(ci膮g dalszy ze strony 1101)
else s
PostMessage (pparams->hwnd, WM CALC 嶣ORTED, 0, 0) ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static HANDLE hEvent ;
static INT iStatus ;
static LONG lTime ;
static PARAMS params ; '
static TCHAR * szMessa9eC7 = f TEXT ("Ready (left mouse button begins)"), ;
TEXT ("Working (right mouse button ends)"),
TEXT ("㩳 repetitions in %ld msec") ) ;
HDC hdc ; (
PAINTSTRUCT ps ;
RECT rect ;
TCHAR szBufferC贸4] ; l
switch (message) ,
I
case WM_CREATE:
;
hEvent = CreateEvent (NULL, FALSE, FALSE, NULL) ;
params.hwnd = hwnd ; ;
params.hEvent = hEvent ; ;
params.bContinue = FALSE ;
,
㧏eginthread (Thread, 0, ¶ms) ;
I
return 0 ;
case WM_LBUTTONDOWN: I
if (iStatus == STATUS WORKING)
MessageBeep (0) ;
return 0 ;
I
iStatus = STATUS WORKING ;
params.bContinue = TRUE ;
SetEvent (hEvent) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_RBUTTONDOWN:
params.bContinue = FALSE ;
return 0 ;
r
case WM CALC DONE:
lTime = lParam ;
iStatus = STATUS﨑ONE ; i
InvalidateRect (hwnd, NULL, TRUE) ; I
return 0 ; ',
case WM_CALC ABORTED:
iStatus = STATUS READY ;
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1103
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
wsprintf (szBuffer, szMessage[iStatus], REP, lTime) ;
DrawText (hdc, szBuffer, -l, &rect,
DTINGLELINE DT CENTER DT VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM﨑ESTROY:
PostOuitMessage (0) ;
return 0 ;
)
return DefWindowProc (hwnd, message, wParam, lParam) ;
Rysunek 20-5. Program BIGJOB2
Procedura okna przetwarza komunikat WM CREATE tworz膮c najpierw obiekt
zdarzenia inicjowany jako niezasygnalizowany (zresetowany). Nast臋pnie proce-
dura tworzy w膮tek.
Funkcja Thread rozpoczyna niesko艅czon膮 p臋tl臋 while, na poc偶膮tku kt贸rej wywo-
艂ywana jest funkcja WaitForSingleObject. Warto zauwa偶y膰, 偶e w strukturze PA-
RAMS jest trzecie pole zawieraj膮ce uchwyt obiektu zdarzenia. Poniewa偶 obiekt
zdarzenia jest pocz膮tkowo niezasygnalizowany, w膮tek zostanie na wywo艂aniu
tej funkcji zawieszony. Klikni臋cie lewego przycisku myszy powoduje, 偶e proce-
dura okna wywo艂a funkcj臋 SetEvent. To z kolei zwolni drugi w膮tek z czekania
w funkcji WaitForSingleObject i w艂膮czy wyliczenia. Po zako艅czeniu wylicze艅 w膮-
tek wywo艂a ponownie WaitForSingleObject, ale obiekt zdarzenia za spraw膮 pierw-
szego wywo艂ania sta艂 si臋 niezasygnalizowany. Zostanie wi臋c zawieszony do cza-
su kolejnego klikni臋cia mysz膮.
Reszta programu jest prawie identyczna z BIGJOBI.
Lokalna pami臋膰 w膮tku (TLS)
Zmienne globalne programu wielow膮tkowego (tak samo jak wszelkie rezerwo-
wane obszary pami臋ci) s膮 wsp贸lne dla jego wszystkich w膮tk贸w. Lokalne zmien-
ne statyczne funkcji s膮 r贸wnie偶 wsp贸lne wszystkim w膮tkom, kt贸re korzystaj膮
z funkcji. Lokalne zmienne automatyczne funkcji s膮 prywatne dla ka偶dego w膮t-
ku, jako przechowywane na stosie, a ka偶dy w膮tek ma sw贸j w艂asny stos.
Przyda艂oby si臋 mie膰 takie miejsce w pami臋ci, kt贸re by艂oby zarazem prywatne dla
danego w膮tku i trwa艂e. Takiego miejsca wymaga na przyk艂ad wspomniana we
wcze艣niejszej cz臋艣ci tego rozdzia艂u funkcja strtok. Niestety, w definicji j臋zyka C
nie przewidziano takich zmiennych. Za to system Windows wyposa偶ono w czte-
1104 Cz臋艣膰 III: Zagadnienia zaawansowane
ry funkcje, za pomoc膮 kt贸rych mo偶na zaimplementowa膰 odpowiedni mechanizm.
Rozwi膮zanie problemu zapewniaj膮 r贸wnie偶 rozszerzenia j臋zyka C opracowane
przez Microsoft. Jak ju偶 wiadomo, mowa o obszarach TLS.
Oto jak dzia艂a API:
Najpierw definiuje si臋 struktur臋 zawieraj膮c膮 wszystkie dane, kt贸re maj膮 by膰 pry-
watne dla w膮tk贸w, na przyk艂ad:
typedef struct
(
int a;
int b;
1
DATA, * PDATA;
W膮tek g艂贸wny wywo艂uje funkcj臋 TlsAlloc, w wyniku kt贸rej otrzymuje indeks:
dwTlsIndex = TlsAlloc ();
Ta warto艣膰 indeksowa mo偶e by膰 zapisana w zmiennej globalnej albo przekazy-
wana funkcji Thread w strukturze argument贸w.
Funkcja Thread zaczyna dzia艂anie od zarezerwowania w pami臋ci miejsca na struk-
tur臋 danych. Rezerwacji dokonuje przez wywo艂anie funkcji TlsSetvalue z uzyska-
nym wcze艣niej indeksem jako parametrem:
TlsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA));
Powoduje to skojarzenie wska藕nika z okre艣lonym w膮tkiem i konkretnym indek-
sem w膮tku. Teraz dowolna funkcja, 艂膮cznie z sam膮 funkcj膮 Thread, mo偶e zrobi膰
u偶ytek ze wska藕nika, wykonuj膮c nast臋puj膮cy kod:
PDATA pdata;
pdata = (PDATA) TlsGetValue (dwTlsIndex);
Teraz mo偶e modyfikowa膰 lub odczytywa膰 warto艣ci pdata->a i pdata->b. Zanim
funkcja Thread zako艅czy dzia艂anie, zwalnia zarezerwowan膮 pami臋膰:
GlobalFree (TlsGetValue (dwTlsIndex));
Kiedy zako艅cz膮 si臋 wszystkie w膮tki u偶ywaj膮ce danych, w膮tek g艂贸wny zwalnia
indeks:
TlsFree (dwTlsIndex);
Ten proces mo偶e pocz膮tkowo wydawa膰 si臋 skomplikowany, dlatego z pewno-
艣ci膮 warto przeanalizowa膰 przyk艂adow膮 implementacj臋 pami臋ci TLS. Nie wiem,
jak robi to Windows, ale wydaje mi si臋, 偶e poni偶szy opis jest prawdopodobny.
Najpierw TlsAlloc mo偶e rezerwowa膰 blok pami臋ci (o d艂ugo艣ci zero) i zwraca膰 in-
deks b臋d膮cy wska藕nikiem do tego bloku. Ka偶de wywo艂anie TlsSetvalue z tym
indeksem powoduje zwi臋kszenie bloku o 8 bajt贸w przez realokacj臋. Na owych
o艣miu bajtach zapisywane s膮 identyfikator w膮tku wywo艂uj膮cego funkcj臋 - uzy-
skany z wywo艂ania GetCurrentThreadld- oraz wska藕nik przekazany funkcji Tls-
Setvalue. Funkcja TlsGetvalue przeszukuje tabel臋, u偶ywaj膮c identyfikatora w膮t-
ku, po czym zwraca wska藕nik. TlsFree zwalnia blok pami臋ci. Jak wi臋c wida膰,
mechanizm mo偶na 艂atwo zaimplementowa膰 samemu, ale mi艂o mie膰 go ju偶 poda-
ny na tacy.
Rozdzia艂 20: Wielozadaniowo艣膰 i wielow膮tkowo艣膰 1105
Korzystaj膮c z rozszerze艅 j臋zyka C opracowanych przez Microsoft, problem mo偶-
na rozwi膮za膰 jeszcze pro艣ciej. Wystarczy poprzedzi膰 zmienn膮, kt贸ra ma by膰 pry-
watna dla ka偶dego w膮tku, prefiksem declspec (thread), na przyk艂ad tak:
㩳eclspec (thread) int iGlobal = 1;
w przypadku zmiennych statycznych zewn臋trznych wzgl臋dem funkcji albo tak:
㩳eclspec (thread) static int iLocal = 2;
w przypadku zmiennych statycznych wewn臋trznych wzgl臋dem funkcji.
Wyszukiwarka
Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold24
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold22
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold04
Programowniae windows petzold Petzold20
Programowniae windows petzold Petzold03
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook
wi臋cej podobnych podstron