Programowanie współbieżne Zadanie 5
Transkrypt
Programowanie współbieżne Zadanie 5
150875 151021 numer indeksu Grzegorz Graczyk imię i nazwisko Data Kierunek Specjalizacja Rok akademicki Semestr numer indeksu Paweł Tarasiuk imię i nazwisko 2011-11-07 Informatyka Inżynieria Oprgoramowania i Analiza Danych 2011/12 7 Programowanie współbieżne Zadanie 5 - Podstawowe problemy programowania współbieżnego Wstęp Zadanie laboratoryjne polegało na implementacji problemu czytelników i pisarzy. Czytelnicy i pisarze rywalizują o dostęp do tego samego zasobu. W tym samym czasie z zasobu może korzystać dowolna liczba czytelników albo dokładnie jeden pisarz. Rozwiązanie W ramach rozwiązania została stworzona klasa RWLock, która posiada metody pozwalające oddzielić kod samych czytelników i pisarzy od samej implementacji rozwiązania problemu. Dzięki temu raz napisany program testujący może być testowany na różnych implementacjach. Przygotowaliśmy trzy propozycje rozwiązania problemu (czyli odmienne implementacje klasy RWLock): Rozwiązanie wstępne Rozwiązanie to wykorzystuje jeden monitor oraz zamek powiązany z tym monitorem. Możliwa jest również implementacja w oparciu o dwa semafory. Czytelnicy zamykają zamek jedynie na czas rozpoczynania i kończenia czytania. Procedury te są sekcjami krytycznymi, a w ich czasie jest modyfikowany licznik czytelników. W wypadku pisarzy zamek jest zakładany w chwili rozpoczęcia pisania. Następnie dopóki istnieją czytelnicy zamek jest otwierany, zaś procesy zatrzymują się na monitorze w oczekiwaniu aż licznik czytelników zostanie wyzerowany. Po zakończeniu oczekiwania proces zamyka zamek i otwiera go po zakończeniu pisania. Rozwiązanie to jest poprawne (w sensie poprawnego ograniczenia dostępu do zasobu) oraz nie zawiera blokad. Nie rozwiązuje ono jednak w żaden sposób problemu zagłodzenia. PSK: Grzegorz Graczyk i Paweł Tarasiuk 2/9 Rozwiązanie z priorytetem dla pisarzy Do powyższego rozwiązania dodajemy licznik oczekujących pisarzy, który będzie zablokowany nowym zamkiem. Od tej chwili każdy czytelnik przed zwiększeniem licznika czytelników upewnia się, że żaden pisarz nie czeka. W przeciwnym wypadku czytelnik zatrzymuje się na monitorze do czasu zakończenia pracy wszystkich pisarzy. Rozwiązanie to rozbudowuje poprzednie rozwiązanie o optymalne wykorzystanie czasu. Dzięki temu do zagłodzenia dochodzi tylko w wypadku, gdy nowi pisarze pojawiają się szybciej niż starzy kończą swoje zadania. PSK: Grzegorz Graczyk i Paweł Tarasiuk 3/9 Rozwiązanie z priorytetem dla oczekujących pisarzy Rozwiązaniem, które naszym zdaniem najlepiej pozbywa się problemu zagłodzenia jest modyfikacja powyższego. Tym razem priorytet jest przyznawany tym pisarzom, którzy czekali w trakcie czytania dowolnego czytelnika. W celu realizacji tej metody licznik pisarzy jest zwiększany dopiero po spełnieniu warunku zmiany licznika czytelników. Pewnym uproszczeniem w stosunku do poprzedniego rozwiązania jest fakt, że czytelnicy po otrzymaniu sygnału na monitorze o obsłużeniu wszystkich pisarzy z priorytetem nie upewniają się czy nie pojawił się nowy pisarz. PSK: Grzegorz Graczyk i Paweł Tarasiuk 4/9 Implementacja Rozwiązania zostały wykonane w języku Python, z wykorzystaniem biblioteki multiprocessing. W implementacji wykorzystywany jest fakt, iż logika monitorów znajduje się w klasie Condition odpowiadającej warunkowi monitora. Faktycznym elementem odpowiadającym monitorowi jest klasa Lock, której instancja jest parametrem konstruktora obiektu warunku. W efekcie implementacja zawiera jedynie jeden zamek co zmniejsza ryzyko popełnienia przez programistę błędu prowadzącego do blokady. Wątkami są czytelnicy oraz pisarze. Ponadto istnieje dodatkowy wątek służący tworzeniu czytelników i pisarzy za pomocą interfejsu graficznego. Interfejs pozwala śledzić stan czytelników i pisarzy - zmiany w tym stanie świadczą o tym, komu po kolei przydzielany jest zasób. Interfejs okienkowy został wykonany w oparciu o bidingi biblioteki gtk dla języka Python. Podsumowanie Problem czytelników i pisarzy można rozwiązywać na wiele sposobów, a ocena które z tych sposobów są lepsze nie jest uniwersalna - wszystko zależy od oczekiwań co do priorytetu pomiędzy czytelnikami a pisarzami. Przygotowane przez nas rozwiązanie z priorytetem dla pisarzy jest popularnym podejściem do problemu, lecz pomysł będący podstawą trzeciej spośród przygotowanych przez nas propozycji rozwiązania stanowi pewien kompromis pomiędzy znaczeniem pisarzy (którzy powinni jak najszybciej aktualizować zasób), a unikaniem zagłodzenia czytelników. PSK: Grzegorz Graczyk i Paweł Tarasiuk 5/9 Kod programu Kod głównej części programu #! / u s r / b i n / env python # −∗− c o d i n g : u t f −8 −∗− # config : writers priority click required entry time working time leaving time = = = = = False True 1 1 1 im po rt pygtk pygtk . r e q u i r e ( ’ 2 . 0 ’ ) im po rt gtk , g o b j e c t from m u l t i p r o c e s s i n g im por t P r o c e s s a s ThreadOrProcess , Queue , Semaphore from time i mpo rt s l e e p w r i t e r s p r i o r i t y == True : print ’ Priorytet pisarzy ’ from p r w l o c k imp ort RWLock e l i f w r i t e r s p r i o r i t y == F a l s e : p r i n t ’ Bez p r i o r y t e t u ’ from r w l o c k i mpo rt RWLock else : print ’ Priorytet oczekujacych pisarzy ’ from x r w l o c k im po rt RWLock if def async ( f u n c t i o n ) : d e f C a l l ( ∗ a r g s , ∗ ∗ kwargs ) : t = ThreadOrProcess ( t a r g e t=f u n c t i o n , a r g s=a r g s ) t . daemon = kwargs . g e t ( ’ daemon ’ , True ) t . start () return t return Call l a b e l q u e u e = Queue ( ) r e s o u r c e = RWLock ( ) @async d e f Reader ( i , s ) : l a b e l q u e u e . put ( ( i , ” C z y t e l n i k %d . [ ]” % i ) ) sleep ( entry time ) r e s o u r c e . acquireRead ( ) l a b e l q u e u e . put ( ( i , ” C z y t e l n i k %d . [x]” % i ) ) s l e e p ( working time ) if click required : s . acquire () resource . releaseRead () l a b e l q u e u e . put ( ( i , ”<− C z y t e l n i k %d . ” % i ) ) sleep ( leaving time ) l a b e l q u e u e . put ( ( i , None ) ) @async def Writer ( i , s ) : l a b e l q u e u e . put ( ( i , ” [ ] P i s a r z %d . ” % i ) ) sleep ( entry time ) resource . acquireWrite () l a b e l q u e u e . put ( ( i , ” [ x ] P i s a r z %d . ” % i ) ) s l e e p ( working time ) if click required : s . acquire () resource . releaseWrite () l a b e l q u e u e . put ( ( i , ” P i s a r z %d . −>” % i ) ) sleep ( leaving time ) l a b e l q u e u e . put ( ( i , None ) ) PSK: Grzegorz Graczyk i Paweł Tarasiuk 6/9 ################################# GUI ################################ c l a s s Window : def update labels ( s e l f ) : w h i l e not l a b e l q u e u e . empty ( ) : i , l a b e l = label queue . get () i f l a b e l i s None : s e l f . buttons [ i ] . destroy ( ) else : s e l f . buttons [ i ] . s e t l a b e l ( l a b e l ) gobject . timeout add (10 , s e l f . u p d a t e l a b e l s ) d e f new button ( s e l f , box , t e x t , t a s k ) : btn = g t k . Button ( ”Nowy %s ” % t e x t . l o w e r ( ) ) btn . c o n n e c t ( ” c l i c k e d ” , s e l f . n e w c l i c k e d , ( box , t a s k ) ) btn . s e t s i z e r e q u e s t ( 2 5 0 , 5 0 ) box . p a c k e n d ( btn , F a l s e , F a l s e ) btn . show ( ) d e f b ut to n ( s e l f , box , s ) : btn = g t k . Button ( ) btn . c o n n e c t ( ” c l i c k e d ” , s e l f . c l i c k e d , ( s ) ) btn . s e t s i z e r e q u e s t ( 2 5 0 , 5 0 ) box . p a c k s t a r t ( btn , F a l s e , F a l s e ) btn . show ( ) r e t u r n btn d e f n e w c l i c k e d ( s e l f , widget , data=None ) : box , Task = data i = len ( s e l f . buttons ) s = Semaphore ( 0 ) s e l f . b u t t o n s += [ s e l f . but t on ( box , s ) ] Task ( i , s ) d e f c l i c k e d ( s e l f , widget , data=None ) : data . r e l e a s e ( ) d e f d e l e t e e v e n t ( s e l f , widget , event , data=None ) : return False d e f d e s t r o y ( s e l f , widget , data=None ) : gtk . main quit ( ) def init ( self ): s e l f . buttons = [ ] s e l f . window = g t k . Window ( g t k .WINDOW TOPLEVEL) s e l f . window . c o n n e c t ( ” d e l e t e e v e n t ” , s e l f . d e l e t e e v e n t ) s e l f . window . c o n n e c t ( ” d e s t r o y ” , s e l f . d e s t r o y ) s e l f . window . s e t b o r d e r w i d t h ( 5 ) c o n t e n t = g t k . HBox ( ) l e f t = g t k . VBox ( ) l e f t . set border width (5) s e l f . new button ( l e f t , ” C z y t e l n i k ” , Reader ) c o n t e n t . add ( l e f t ) l e f t . show ( ) s e p = g t k . V Separ ator ( ) c o n t e n t . add ( s e p ) s e p . show ( ) r i g h t = g t k . VBox ( ) right . set border width (5) s e l f . new button ( r i g h t , ” P i s a r z ” , W r i t e r ) c o n t e n t . add ( r i g h t ) r i g h t . show ( ) s e l f . window . add ( c o n t e n t ) PSK: Grzegorz Graczyk i Paweł Tarasiuk 7/9 c o n t e n t . show ( ) s e l f . window . show ( ) d e f main ( s e l f ) : gobject . timeout add (1000 , s e l f . u p d a t e l a b e l s ) g t k . main ( ) Window ( ) . main ( ) Kod rozwiązania najprostszego from m u l t i p r o c e s s i n g im por t Lock , Value , C o n d i t i o n c l a s s RWLock : def init ( self ): s e l f . l o c k = Lock ( ) s e l f . condition = Condition ( s e l f . lock ) s e l f . r e a d e r s = Value ( ’ i ’ , 0 , l o c k=F a l s e ) def acquireRead ( s e l f ) : s e l f . lock . acquire () s e l f . r e a d e r s . v a l u e += 1 s e l f . lock . release () def acquireWrite ( s e l f ) : s e l f . lock . acquire () while s e l f . readers . value > 0: s e l f . c o n d i t i o n . wait ( ) def releaseRead ( s e l f ) : s e l f . lock . acquire () s e l f . r e a d e r s . v a l u e −= 1 i f s e l f . r e a d e r s . v a l u e == 0 : s e l f . condition . n o t i f y a l l () s e l f . lock . release () def releaseWrite ( s e l f ) : s e l f . lock . release () Kod rozwiązania z priorytetem czytelników from m u l t i p r o c e s s i n g im por t Lock , Value , C o n d i t i o n c l a s s RWLock : def init ( self ): s e l f . l o c k = Lock ( ) s e l f . condition = Condition ( s e l f . lock ) s e l f . r e a d e r s = Value ( ’ i ’ , 0 , l o c k=F a l s e ) s e l f . wcondition = Condition ( s e l f . lock ) s e l f . w r i t e r s = Value ( ’ i ’ , 0 ) def acquireRead ( s e l f ) : s e l f . lock . acquire () while s e l f . writers . value > 0: s e l f . wcondition . wait ( ) s e l f . r e a d e r s . v a l u e += 1 s e l f . lock . release () def acquireWrite ( s e l f ) : s e l f . w r i t e r s . v a l u e += 1 s e l f . lock . acquire () while s e l f . readers . value > 0: s e l f . c o n d i t i o n . wait ( ) def releaseRead ( s e l f ) : s e l f . lock . acquire () s e l f . r e a d e r s . v a l u e −= 1 i f s e l f . r e a d e r s . v a l u e == 0 : s e l f . condition . n o t i f y a l l () PSK: Grzegorz Graczyk i Paweł Tarasiuk 8/9 s e l f . lock . release () def releaseWrite ( s e l f ) : s e l f . w r i t e r s . v a l u e −= 1 i f s e l f . w r i t e r s . v a l u e == 0 : s e l f . wcondition . n o t i f y a l l () s e l f . lock . release () Kod rozwiązania z priorytetem oczekujących czytelników from m u l t i p r o c e s s i n g im por t Lock , Value , C o n d i t i o n c l a s s RWLock : init ( self ): def s e l f . l o c k = Lock ( ) s e l f . condition = Condition ( s e l f . lock ) s e l f . r c o n d i t i o n = Condition ( s e l f . lock ) s e l f . r e a d e r s = Value ( ’ i ’ , 0 , l o c k=F a l s e ) s e l f . f i r s t r e a d e r = Value ( ’ f ’ , 0 , l o c k=F a l s e ) s e l f . w r i t e r s = Value ( ’ i ’ , 0 , l o c k=F a l s e ) def acquireRead ( s e l f ) : s e l f . lock . acquire () i f s e l f . writers : s e l f . r c o n d i t i o n . wait ( ) s e l f . r e a d e r s . v a l u e += 1 s e l f . lock . release () def acquireWrite ( s e l f ) : s e l f . lock . acquire () forcing = False while s e l f . readers . value > 0: s e l f . c o n d i t i o n . wait ( ) i f not f o r c i n g : s e l f . w r i t e r s . v a l u e += 1 f o r c i n g = True i f forcing : s e l f . w r i t e r s . v a l u e −= 1 i f s e l f . w r i t e r s . v a l u e == 0 : s e l f . rcondition . n o t i f y a l l () def releaseRead ( s e l f ) : s e l f . lock . acquire () s e l f . r e a d e r s . v a l u e −= 1 s e l f . condition . n o t i f y a l l () s e l f . lock . release () def releaseWrite ( s e l f ) : s e l f . lock . release () PSK: Grzegorz Graczyk i Paweł Tarasiuk 9/9