Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00263 008592 10442134 na godz. na dobę w sumie
Efektywny Python. 59 sposobów na lepszy kod - ebook/pdf
Efektywny Python. 59 sposobów na lepszy kod - ebook/pdf
Autor: Liczba stron: 232
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-1543-3 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> python - programowanie
Porównaj ceny (książka, ebook (-20%), audiobook).

Twórz zoptymalizowany i efektywny kod!

Python to jeden z najstarszych używanych języków programowania. Co ciekawe, jego nazwa wcale nie pochodzi od zwierzęcia, a od popularnego serialu komediowego. Język ten daje programistom ogromne pole do popisu, a ponadto posiada sporo bibliotek realizujących najbardziej wymyślne zadania. Z uwagi na te atuty rozpoczęcie programowania w tym języku nie powinno przysporzyć Ci większych problemów. Jeżeli jednak chcesz robić to efektywnie, potrzebujesz tej książki.

Sięgnij po nią i poznaj 59 sposobów na tworzenie lepszego kodu w Pythonie! W kolejnych rozdziałach znajdziesz bezcenne informacje na temat programowania zgodnego z duchem Pythona, funkcji, klas i dziedziczenia oraz metaklas i atrybutów. Dalsze strony zawierają przydatną wiedzę na temat wątków i współbieżności, wbudowanych modułów oraz sposobów zarządzania kodem. Książka ta sprawdzi się w rękach każdego programisty pracującego w języku Python. Warto ją mieć!

W książce poruszono następujące zagadnienia:

Poznaj najlepsze praktyki programowania w Pythonie!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: Effective Python: 59 Specific Ways to Write Better Python (Effective Software Development Series) Tłumaczenie: Robert Górczyński ISBN: 978-83-283-1540-2 Authorized translation from the English language edition, entitled: EFFECTIVE PYTHON: 59 SPECIFIC WAYS TO WRITE BETTER PYTHON; ISBN 0134034287; by Brett Slatkin; published by Pearson Education, Inc, publishing as Addison Wesley Professional. Copyright © 2015 by Pearson Education, Inc. All rights reserved. No part of this book may by reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. Polish language edition published by HELION S.A. Copyright © 2015. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/efepyt Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis tre(cid:292)ci Wprowadzenie ................................................................................ 11 Podzi(cid:269)kowania ............................................................................... 15 O autorze ....................................................................................... 17 Rozdzia(cid:227) 1. Programowanie zgodne z duchem Pythona ................... 19 Sposób 1. Ustalenie u(cid:304)ywanej wersji Pythona ........................................... 19 Sposób 2. Stosuj styl PEP 8 ...................................................................... 21 Sposób 3. Ró(cid:304)nice mi(cid:269)dzy typami bytes, str i unicode ............................... 23 Sposób 4. Decyduj si(cid:269) na funkcje pomocnicze zamiast na skomplikowane wyra(cid:304)enia ....................................... 26 Sposób 5. Umiej(cid:269)tnie podziel sekwencje .................................................... 29 Sposób 6. Unikaj u(cid:304)ycia indeksów pocz(cid:264)tek, koniec i warto(cid:292)ci kroku w pojedynczej operacji podzia(cid:227)u ...................... 31 Sposób 7. U(cid:304)ywaj list sk(cid:227)adanych zamiast funkcji map() i filter() ................... 33 Sposób 8. Unikaj wi(cid:269)cej ni(cid:304) dwóch wyra(cid:304)e(cid:281) na li(cid:292)cie sk(cid:227)adanej .................... 35 Sposób 9. Rozwa(cid:304) u(cid:304)ycie generatora wyra(cid:304)e(cid:281) dla du(cid:304)ych list sk(cid:227)adanych ....36 Sposób 10. Preferuj u(cid:304)ycie funkcji enumerate() zamiast range() ...................... 38 Sposób 11. U(cid:304)ycie funkcji zip() do równoczesnego przetwarzania iteratorów ... 39 Sposób 12. Unikaj bloków else po p(cid:269)tlach for i while ................................... 41 Sposób 13. Wykorzystanie zalet wszystkich bloków w konstrukcji try-except-else-finally ......................................... 44 Rozdzia(cid:227) 2. Funkcje ....................................................................... 47 Sposób 14. Preferuj wyj(cid:264)tki zamiast zwrotu warto(cid:292)ci None .......................... 47 Sposób 15. Zobacz, jak domkni(cid:269)cia wspó(cid:227)dzia(cid:227)aj(cid:264) z zakresem zmiennej ...... 49 Sposób 16. Rozwa(cid:304) u(cid:304)ycie generatorów, zamiast zwraca(cid:254) listy ...................... 54 Poleć książkęKup książkę 8 Spis tre(cid:292)ci Sposób 17. Podczas iteracji przez argumenty zachowuj postaw(cid:269) defensywn(cid:264) .................................................. 56 Sposób 18. Zmniejszenie wizualnego zagmatwania za pomoc(cid:264) zmiennej liczby argumentów pozycyjnych ................ 61 Sposób 19. Zdefiniowanie zachowania opcjonalnego za pomoc(cid:264) argumentów w postaci s(cid:227)ów kluczowych .................. 63 Sposób 20. U(cid:304)ycie None i docstring w celu dynamicznego okre(cid:292)lenia argumentów domy(cid:292)lnych ................... 66 Sposób 21. Wymuszaj czytelno(cid:292)(cid:254) kodu, stosuj(cid:264)c jedynie argumenty w postaci s(cid:227)ów kluczowych ............ 69 Rozdzia(cid:227) 3. Klasy i dziedziczenie ....................................................73 Sposób 22. Preferuj klasy pomocnicze zamiast s(cid:227)owników i krotek .............. 73 Sposób 23. Dla prostych interfejsów akceptuj funkcje zamiast klas ............ 78 Sposób 24. U(cid:304)ycie polimorfizmu @classmethod w celu ogólnego tworzenia obiektów .......................................... 82 Sposób 25. Inicjalizacja klasy nadrz(cid:269)dnej za pomoc(cid:264) wywo(cid:227)ania super() ...... 87 Sposób 26. Wielokrotnego dziedziczenia u(cid:304)ywaj jedynie w klasach narz(cid:269)dziowych .................................. 91 Sposób 27. Preferuj atrybuty publiczne zamiast prywatnych ....................... 95 Sposób 28. Dziedziczenie po collections.abc w kontenerach typów niestandardowych ................................... 99 Rozdzia(cid:227) 4. Metaklasy i atrybuty ................................................... 105 Sposób 29. U(cid:304)ywaj zwyk(cid:227)ych atrybutów zamiast metod typu getter i setter ...105 Sposób 30. Rozwa(cid:304) u(cid:304)ycie @property zamiast refaktoryzacji atrybutów ..... 109 Sposób 31. Stosuj deskryptory, aby wielokrotnie wykorzystywa(cid:254) metody udekorowane przez @property .................................... 113 Sposób 32. U(cid:304)ywaj metod __getattr__(), __getattribute__() i __setattr__() dla opó(cid:302)nionych atrybutów ..................................................... 117 Sposób 33. Sprawdzaj podklasy za pomoc(cid:264) metaklas ................................ 122 Sposób 34. Rejestruj istniej(cid:264)ce klasy wraz z metaklasami ......................... 124 Sposób 35. Adnotacje atrybutów klas dodawaj za pomoc(cid:264) metaklas .......... 128 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) ..................................... 131 Sposób 36. U(cid:304)ywaj modu(cid:227)u subprocess do zarz(cid:264)dzania procesami potomnymi ..................................... 132 Sposób 37. U(cid:304)ycie w(cid:264)tków dla operacji blokuj(cid:264)cych wej(cid:292)cie-wyj(cid:292)cie, unikanie równoleg(cid:227)o(cid:292)ci ........................................................... 136 Sposób 38. U(cid:304)ywaj klasy Lock, aby unika(cid:254) stanu wy(cid:292)cigu w w(cid:264)tkach ....... 140 Sposób 39. U(cid:304)ywaj klasy Queue do koordynacji pracy mi(cid:269)dzy w(cid:264)tkami ..... 143 Poleć książkęKup książkę Spis tre(cid:292)ci 9 Sposób 40. Rozwa(cid:304) u(cid:304)ycie wspó(cid:227)programów w celu jednoczesnego wykonywania wielu funkcji ................... 150 Sposób 41. Rozwa(cid:304) u(cid:304)ycie concurrent.futures(), aby otrzyma(cid:254) prawdziw(cid:264) równoleg(cid:227)o(cid:292)(cid:254) .................................... 158 Rozdzia(cid:227) 6. Wbudowane modu(cid:227)y ....................................................163 Sposób 42. Dekoratory funkcji definiuj za pomoc(cid:264) functools.wraps ........... 163 Sposób 43. Rozwa(cid:304) u(cid:304)ycie polece(cid:281) contextlib i with w celu uzyskania wielokrotnego u(cid:304)ycia konstrukcji try-finally .... 166 Sposób 44. Niezawodne u(cid:304)ycie pickle wraz z copyreg ................................ 169 Sposób 45. Podczas obs(cid:227)ugi czasu lokalnego u(cid:304)ywaj modu(cid:227)u datetime zamiast time ........................................................................... 174 Sposób 46. U(cid:304)ywaj wbudowanych algorytmów i struktur danych .............. 178 Sposób 47. Gdy wa(cid:304)na jest precyzja, u(cid:304)ywaj modu(cid:227)u decimal ................... 183 Sposób 48. Kiedy szuka(cid:254) modu(cid:227)ów opracowanych przez spo(cid:227)eczno(cid:292)(cid:254)? ....... 185 Rozdzia(cid:227) 7. Wspó(cid:227)praca .................................................................187 Sposób 49. Dla ka(cid:304)dej funkcji, klasy i modu(cid:227)u utwórz docstring ............... 187 Sposób 50. U(cid:304)ywaj pakietów do organizacji modu(cid:227)ów i dostarczania stabilnych API .................................................. 191 Sposób 51. Zdefiniuj g(cid:227)ówny wyj(cid:264)tek Exception w celu odizolowania komponentu wywo(cid:227)uj(cid:264)cego od API ........... 196 Sposób 52. Zobacz, jak przerwa(cid:254) kr(cid:264)g zale(cid:304)no(cid:292)ci ...................................... 199 Sposób 53. U(cid:304)ywaj (cid:292)rodowisk wirtualnych dla odizolowanych i powtarzalnych zale(cid:304)no(cid:292)ci ......................... 204 Rozdzia(cid:227) 8. Produkcja ...................................................................211 Sposób 54. Rozwa(cid:304) u(cid:304)ycie kodu o zasi(cid:269)gu modu(cid:227)u w celu konfiguracji (cid:292)rodowiska wdro(cid:304)enia ............................... 211 Sposób 55. U(cid:304)ywaj ci(cid:264)gów tekstowych repr do debugowania danych wyj(cid:292)ciowych ..................................... 214 Sposób 56. Testuj wszystko za pomoc(cid:264) unittest ........................................ 217 Sposób 57. Rozwa(cid:304) interaktywne usuwanie b(cid:227)(cid:269)dów za pomoc(cid:264) pdb ........... 220 Sposób 58. Przed optymalizacj(cid:264) przeprowadzaj profilowanie ...................... 222 Sposób 59. Stosuj modu(cid:227) tracemalloc, aby pozna(cid:254) sposób u(cid:304)ycia pami(cid:269)ci i wykry(cid:254) jej wycieki ................................................................. 226 Skorowidz ........................................................................................229 Poleć książkęKup książkę 10 Spis tre(cid:292)ci Poleć książkęKup książkę Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) wyst(cid:269)puje wtedy, gdy komputer pozornie wykonuje jednocze- (cid:292)nie wiele ró(cid:304)nych zada(cid:281). Na przyk(cid:227)ad w komputerze wyposa(cid:304)onym w pro- cesor o tylko jednym rdzeniu system operacyjny b(cid:269)dzie bardzo szybko zmienia(cid:227) aktualnie wykonywany program na inny. Tym samym programy s(cid:264) wykonywane na przemian, co tworzy iluzj(cid:269) ich jednoczesnego dzia(cid:227)ania. Z kolei równoleg(cid:227)o(cid:292)(cid:254) to faktyczne wykonywanie jednocze(cid:292)nie wielu ró(cid:304)nych zada(cid:281). Je(cid:304)eli komputer jest wyposa(cid:304)ony w wielordzeniowy procesor, to po- szczególne rdzenie mog(cid:264) jednocze(cid:292)nie wykonywa(cid:254) ró(cid:304)ne zadania. Poniewa(cid:304) poszczególne rdzenie procesora wykonuj(cid:264) polecenia innego programu, wi(cid:269)c poszczególne aplikacje dzia(cid:227)aj(cid:264) jednocze(cid:292)nie i w tym samym czasie ka(cid:304)da z nich odnotowuje post(cid:269)p w dzia(cid:227)aniu. W ramach jednego programu wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) to narz(cid:269)dzie u(cid:227)atwiaj(cid:264)ce pro- gramistom rozwi(cid:264)zywanie pewnego rodzaju problemów. Programy wspó(cid:227)bie(cid:304)- ne pozwalaj(cid:264) na zastosowanie wielu ró(cid:304)nych (cid:292)cie(cid:304)ek dzia(cid:227)ania, aby u(cid:304)yt- kownik mia(cid:227) wra(cid:304)enie, (cid:304)e poszczególne operacje w programie odbywaj(cid:264) si(cid:269) jednocze(cid:292)nie i niezale(cid:304)nie. Kluczowa ró(cid:304)nica mi(cid:269)dzy wspó(cid:227)bie(cid:304)no(cid:292)ci(cid:264) i równoleg(cid:227)o(cid:292)ci(cid:264) to szybko(cid:292)(cid:254). Kiedy w programie s(cid:264) stosowane dwie oddzielne (cid:292)cie(cid:304)ki jego wykonywania, to czas potrzebny na wykonanie ca(cid:227)ego zadania programu zmniejsza si(cid:269) o po(cid:227)ow(cid:269). Wspó(cid:227)czynnik szybko(cid:292)ci wykonywania wynosi wi(cid:269)c dwa. Z kolei wspó(cid:227)bie(cid:304)nie dzia(cid:227)aj(cid:264)ce programy mog(cid:264) wykonywa(cid:254) tysi(cid:264)ce oddzielnych (cid:292)cie(cid:304)ek dzia(cid:227)ania, ale to nie prze(cid:227)o(cid:304)y si(cid:269) w ogóle na zmniejszenie ilo(cid:292)ci czasu, jaki jest potrzebny na wykonanie ca(cid:227)ej pracy. Python u(cid:227)atwia tworzenie programów wspó(cid:227)bie(cid:304)nych. Ponadto jest u(cid:304)ywany do równoleg(cid:227)ego wykonywania zada(cid:281) za pomoc(cid:264) wywo(cid:227)a(cid:281) systemowych, podprocesów oraz rozszerze(cid:281) utworzonych w j(cid:269)zyku C. Jednak osi(cid:264)gni(cid:269)cie Poleć książkęKup książkę 132 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) stanu, w którym wspó(cid:227)bie(cid:304)ny kod Pythona b(cid:269)dzie faktycznie wykonywany równolegle, mo(cid:304)e by(cid:254) bardzo trudne. Dlatego te(cid:304) niezwykle wa(cid:304)ne jest po- znanie najlepszych sposobów wykorzystania Pythona w tych nieco odmien- nych sytuacjach. Sposób 36. U(cid:304)ywaj modu(cid:227)u subprocess do zarz(cid:264)dzania procesami potomnymi Python oferuje zaprawione w bojach biblioteki przeznaczone do wykonywa- nia procesów potomnych i zarz(cid:264)dzania nimi. Tym samym Python staje si(cid:269) doskona(cid:227)ym j(cid:269)zykiem do (cid:227)(cid:264)czenia ze sob(cid:264) innych narz(cid:269)dzi, na przyk(cid:227)ad dzia(cid:227)aj(cid:264)cych w pow(cid:227)oce. Kiedy istniej(cid:264)ce skrypty pow(cid:227)oki z czasem staj(cid:264) si(cid:269) skomplikowane, jak to cz(cid:269)sto si(cid:269) zdarza, wówczas przepisanie ich w Pytho- nie jest naturalnym wyborem w celu zachowania czytelno(cid:292)ci kodu i mo(cid:304)li- wo(cid:292)ci jego dalszej obs(cid:227)ugi. Procesy potomne uruchamiane przez Pythona mog(cid:264) dzia(cid:227)a(cid:254) równolegle, a tym samym Python mo(cid:304)e wykorzysta(cid:254) wszystkie rdzenie komputera i zmak- symalizowa(cid:254) przepustowo(cid:292)(cid:254) aplikacji. Wprawdzie sam Python mo(cid:304)e by(cid:254) ogra- niczany przez procesor (patrz sposób 37.), ale bardzo (cid:227)atwo wykorzysta(cid:254) ten j(cid:269)zyk do koordynowania zada(cid:281) obci(cid:264)(cid:304)aj(cid:264)cych procesor. Na przestrzeni lat Python oferowa(cid:227) wiele sposobów uruchamiania podpro- cesów, mi(cid:269)dzy innymi za pomoc(cid:264) wywo(cid:227)a(cid:281) popen, popen2 i os.exec*. Obecnie najlepszym i najprostszym rozwi(cid:264)zaniem w zakresie zarz(cid:264)dzania procesami potomnymi jest u(cid:304)ycie wbudowanego modu(cid:227)u subprocess. Uruchomienie podprocesu za pomoc(cid:264) modu(cid:227)u subprocess jest proste. W po- ni(cid:304)szym fragmencie kodu konstruktor klasy Popen uruchamia proces. Z kolei metoda communicate() odczytuje dane wyj(cid:292)ciowe procesu potomnego i czeka na jego zako(cid:281)czenie. proc = subprocess.Popen( [ echo , Witaj z procesu potomnego! ], stdout=subprocess.PIPE) out, err = proc.communicate() print(out.decode( utf-8 )) Witaj z procesu potomnego! Procesy potomne b(cid:269)d(cid:264) dzia(cid:227)a(cid:227)y niezale(cid:304)nie od ich procesu nadrz(cid:269)dnego, czyli interpretera Pythona. Ich stan mo(cid:304)na okresowo sprawdza(cid:254), gdy Python wykonuje inne zadania. proc = subprocess.Popen([ sleep , 0.3 ]) while proc.poll() is None: print( Pracuj(cid:250)... ) # Miejsce na zadania, których wykonanie wymaga dużo czasu. Poleć książkęKup książkę Sposób 36. U(cid:304)ywaj modu(cid:227)u subprocess do zarz(cid:264)dzania procesami potomnymi 133 # ... print( Kod wyj(cid:295)cia , proc.poll()) Pracuj(cid:250)... Pracuj(cid:250)... Kod wyj(cid:295)cia 0 Oddzielenie procesów potomnego i nadrz(cid:269)dnego oznacza, (cid:304)e proces nadrz(cid:269)dny mo(cid:304)e równocze(cid:292)nie uruchomi(cid:254) dowoln(cid:264) liczb(cid:269) procesów potomnych. Mo(cid:304)na to zrobi(cid:254), uruchamiaj(cid:264)c jednocze(cid:292)nie wszystkie procesy potomne. def run_sleep(period): proc = subprocess.Popen([ sleep , str(period)]) return proc start = time() procs = [] for _ in range(10): proc = run_sleep(0.1) procs.append(proc) Nast(cid:269)pnie mo(cid:304)na czeka(cid:254) na zako(cid:281)czenie przez nie operacji wej(cid:292)cia-wyj(cid:292)cia i zako(cid:281)czy(cid:254) ich dzia(cid:227)anie za pomoc(cid:264) metody communicate(). for proc in procs: proc.communicate() end = time() print( Zako(cid:275)czono w ci(cid:230)gu .3f sekund (end - start)) Zako(cid:275)czono w ci(cid:230)gu 0.117 sekund Wskazówka Jeżeli wymienione procesy działają w sekwencji, to całkowite opóźnienie wynosi sekundę, a nie tylko mniej więcej 0,1 sekundy, jak to zostało zmierzone w omawianym programie. Istnieje równie(cid:304) mo(cid:304)liwo(cid:292)(cid:254) potokowania danych z programu Pythona do podprocesów oraz pobierania ich danych wyj(cid:292)ciowych. Tym samym mo(cid:304)na wykorzysta(cid:254) inne programy do równoczesnego dzia(cid:227)ania. Na przyk(cid:227)ad przyj- mujemy za(cid:227)o(cid:304)enie, (cid:304)e narz(cid:269)dzie pow(cid:227)oki openssl jest u(cid:304)ywane do szyfrowa- nia pewnych danych. Uruchomienie procesu potomnego wraz z argumen- tami pochodz(cid:264)cymi z pow(cid:227)oki oraz potokowanie wej(cid:292)cia-wyj(cid:292)cia jest (cid:227)atwe. def run_openssl(data): env = os.environ.copy() env[ password ] = b \xe24U\n\xd0Ql3S\x11 proc = subprocess.Popen( [ openssl , enc , -des3 , -pass , env:password ], env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE) proc.stdin.write(data) proc.stdin.flush() # Gwarantujemy, że proces potomny otrzyma dane wejściowe. return proc Poleć książkęKup książkę 134 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) W przedstawionym fragmencie kodu potokujemy losowo wygenerowane bajty do funkcji szyfruj(cid:264)cej. W praktyce b(cid:269)d(cid:264) to dane wej(cid:292)ciowe podane przez u(cid:304)yt- kownika, uchwyt do pliku, gniazdo sieciowe itd. procs = [] for _ in range(3): data = os.urandom(10) proc = run_openssl(data) procs.append(proc) Procesy potomne b(cid:269)d(cid:264) dzia(cid:227)a(cid:227)y równolegle z nadrz(cid:269)dnym, a tak(cid:304)e b(cid:269)d(cid:264) ko- rzysta(cid:227)y z danych wej(cid:292)ciowych procesów nadrz(cid:269)dnych. W poni(cid:304)szym kodzie czekamy na zako(cid:281)czenie dzia(cid:227)ania procesów potomnych, a nast(cid:269)pnie po- bieramy wygenerowane przez nie ostateczne dane wyj(cid:292)ciowe. for proc in procs: out, err = proc.communicate() print(out[-10:]) b o4,G\x91\x95\xfe\xa0\xaa\xb7 b \x0b\x01\\\xb1\xb7\xfb\xb2C\xe1b b ds\xc5\xf4;j\x1f\xd0c- Mo(cid:304)na te(cid:304) tworzy(cid:254) (cid:227)a(cid:281)cuchy równocze(cid:292)nie dzia(cid:227)aj(cid:264)cych procesów, podobnie jak potoków w systemie UNIX, u(cid:304)ywaj(cid:264)c danych wyj(cid:292)ciowych jednego pro- cesu potomnego jako danych wej(cid:292)ciowych innego procesu potomnego itd. Poni(cid:304)ej przedstawi(cid:227)em funkcj(cid:269) uruchamiaj(cid:264)c(cid:264) proces potomny, który z kolei spowoduje, (cid:304)e polecenie pow(cid:227)oki md5 pobierze strumie(cid:281) danych wej(cid:292)ciowych: def run_md5(input_stdin): proc = subprocess.Popen( [ md5 ], stdin=input_stdin, stdout=subprocess.PIPE) return proc Wskazówka Wbudowany moduł Pythona o nazwie hashlib oferuje funkcję md5(), a więc uruchomienie te- go rodzaju procesu potomnego nie zawsze jest konieczne. Moim celem jest tutaj pokazanie, jak podprocesy mogą potokować dane wejściowe i wyjściowe. Teraz wykorzystujemy zbiór procesów openssl do szyfrowania pewnych da- nych, a kolejny zbiór procesów do utworzenia warto(cid:292)ci hash na podstawie zaszyfrowanych danych. input_procs = [] hash_procs = [] for _ in range(3): data = os.urandom(10) proc = run_openssl(data) input_procs.append(proc) hash_proc = run_md5(proc.stdout) hash_procs.append(hash_proc) Poleć książkęKup książkę Sposób 36. U(cid:304)ywaj modu(cid:227)u subprocess do zarz(cid:264)dzania procesami potomnymi 135 Operacje wej(cid:292)cia-wyj(cid:292)cia mi(cid:269)dzy procesami potomnymi b(cid:269)d(cid:264) zachodzi(cid:227)y au- tomatycznie po uruchomieniu procesów. Twoim zadaniem jest jedynie za- czeka(cid:254) na zako(cid:281)czenie dzia(cid:227)ania procesów potomnych i wy(cid:292)wietli(cid:254) ostateczne wyniki ich dzia(cid:227)ania. for proc in input_procs: proc.communicate() for proc in hash_procs: out, err = proc.communicate() print(out.strip()) b 7a1822875dcf9650a5a71e5e41e77bf3 b d41d8cd98f00b204e9800998ecf8427e b 1720f581cfdc448b6273048d42621100 Je(cid:304)eli masz obawy, (cid:304)e procesy potomne nigdy si(cid:269) nie zako(cid:281)cz(cid:264) lub co(cid:292) b(cid:269)- dzie blokowa(cid:227)o potoki danych wej(cid:292)ciowych b(cid:264)d(cid:302) wyj(cid:292)ciowych, to upewnij si(cid:269), czy metodzie communicate() zosta(cid:227) przekazany parametr timeout. Przekazanie tego parametru sprawi, (cid:304)e nast(cid:264)pi zg(cid:227)oszenie wyj(cid:264)tku, je(cid:292)li proces potomny nie udzieli odpowiedzi w podanym czasie. Tym samym zyskasz mo(cid:304)liwo(cid:292)(cid:254) za- ko(cid:281)czenia dzia(cid:227)ania nieprawid(cid:227)owo zachowuj(cid:264)cego si(cid:269) procesu potomnego. proc = run_sleep(10) try: proc.communicate(timeout=0.1) except subprocess.TimeoutExpired: proc.terminate() proc.wait() print( Kod wyj(cid:295)cia , proc.poll()) Kod wyj(cid:295)cia -15 Niestety, parametr timeout jest dost(cid:269)pny jedynie w Pythonie 3.3 oraz no- wych wydaniach. We wcze(cid:292)niejszych wersjach Pythona konieczne jest u(cid:304)y- cie wbudowanego modu(cid:227)u select w proc.stdin, proc.stdout i proc.stderr w celu wymuszenia stosowania limitu czasu w trakcie operacji wej(cid:292)cia-wyj(cid:292)cia. Do zapami(cid:269)tania (cid:141) U(cid:304)ywaj modu(cid:227)u subprocess do uruchamiania procesów potomnych oraz zarz(cid:264)dzania ich strumieniami danych wej(cid:292)ciowych i wyj(cid:292)ciowych. (cid:141) Procesy potomne dzia(cid:227)aj(cid:264) równolegle wraz z interpreterem Pythona, co pozwala na maksymalne wykorzystanie dost(cid:269)pnego procesora. (cid:141) U(cid:304)ywaj parametru timeout w metodzie communicate(), aby unika(cid:254) zakleszcze(cid:281) i zawieszenia procesów potomnych. Poleć książkęKup książkę 136 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) Sposób 37. U(cid:304)ycie w(cid:264)tków dla operacji blokuj(cid:264)cych wej(cid:292)cie-wyj(cid:292)cie, unikanie równoleg(cid:227)o(cid:292)ci Sposób 37. U(cid:304)ycie w(cid:264)tków dla operacji blokuj(cid:264)cych wej(cid:292)cie-wyj(cid:292)cie Standardowa implementacja Pythona nosi nazw(cid:269) CPython. Implementacja ta uruchamia program Pythona w dwóch krokach. Pierwszy to przetworze- nie i kompilacja kodu (cid:302)ród(cid:227)owego na kod bajtowy. Drugi to uruchomienie kodu bajtowego za pomoc(cid:264) interpretera opartego na stosie. Wspomniany interpreter kodu bajtowego ma stan, który musi by(cid:254) obs(cid:227)ugiwany i spójny podczas wykonywania programu Pythona. J(cid:269)zyk Python wymusza spójno(cid:292)(cid:254) za pomoc(cid:264) mechanizmu o nazwie GIL (ang. global interpreter lock). W gruncie rzeczy mechanizm GIL to rodzaj wzajemnego wykluczania (mutex) chroni(cid:264)cy CPython przed wp(cid:227)ywem wyw(cid:227)aszczenia wielow(cid:264)tkowego, gdy je- den w(cid:264)tek przejmuje kontrol(cid:269) nad programem przez przerwanie dzia(cid:227)ania innego w(cid:264)tku. Tego rodzaju przerwanie mo(cid:304)e doprowadzi(cid:254) do uszkodzenia interpretera, je(cid:292)li wyst(cid:264)pi w nieoczekiwanym czasie. Mechanizm GIL chroni przed wspomnianymi przerwaniami i gwarantuje, (cid:304)e ka(cid:304)da instrukcja kodu bajtowego dzia(cid:227)a poprawnie z implementacj(cid:264) CPython oraz jej modu(cid:227)ami rozszerze(cid:281) utworzonych w j(cid:269)zyku C. Mechanizm GIL powoduje pewien wa(cid:304)ny negatywny efekt uboczny. W przy- padku programów utworzonych w j(cid:269)zykach takich jak C++ lub Java wiele w(cid:264)tków wykonywania oznacza, (cid:304)e program mo(cid:304)e jednocze(cid:292)nie wykorzysta(cid:254) wiele rdzeni procesora. Wprawdzie Python obs(cid:227)uguje wiele w(cid:264)tków wykony- wania, ale mechanizm GIL powoduje, (cid:304)e w danej chwili tylko jeden z nich robi post(cid:269)p. Dlatego te(cid:304) je(cid:292)li si(cid:269)gasz po w(cid:264)tki w celu przeprowadzania rów- noleg(cid:227)ych oblicze(cid:281) i przy(cid:292)pieszenia programów Pythona, to b(cid:269)dziesz srodze zawiedziony. Przyjmujemy za(cid:227)o(cid:304)enie, (cid:304)e chcesz w Pythonie wykona(cid:254) zadanie wymagaj(cid:264)ce du(cid:304)ej ilo(cid:292)ci oblicze(cid:281). U(cid:304)yjemy algorytmu rozk(cid:227)adu liczby na czynniki. def factorize(number): for i in range(1, number + 1): if number i == 0: yield i Rozk(cid:227)ad zbioru liczb mo(cid:304)e wymaga(cid:254) ca(cid:227)kiem du(cid:304)ej ilo(cid:292)ci czasu. numbers = [2139079, 1214759, 1516637, 1852285] start = time() for number in numbers: list(factorize(number)) end = time() print( Operacja zabra(cid:273)a .3f sekund (end - start)) Operacja zabra(cid:273)a 1.040 sekund W innych j(cid:269)zykach programowania u(cid:304)ycie wielu w(cid:264)tków b(cid:269)dzie mia(cid:227)o sens, poniewa(cid:304) wówczas wykorzystasz wszystkie rdzenie dost(cid:269)pne w procesorze. Poleć książkęKup książkę Sposób 37. U(cid:304)ycie w(cid:264)tków dla operacji blokuj(cid:264)cych wej(cid:292)cie-wyj(cid:292)cie 137 Spróbujmy to zrobi(cid:254) w Pythonie. Poni(cid:304)ej zdefiniowa(cid:227)em w(cid:264)tek Pythona prze- znaczony do przeprowadzenia tych samych oblicze(cid:281) co wcze(cid:292)niej: from threading import Thread class FactorizeThread(Thread): def __init__(self, number): super().__init__() self.number = number def run(self): self.factors = list(factorize(self.number)) Teraz uruchamiam w(cid:264)tki w celu równoleg(cid:227)ego rozk(cid:227)adu poszczególnych liczb. start = time() threads = [] for number in numbers: thread = FactorizeThread(number) thread.start() threads.append(thread) Pozosta(cid:227)o ju(cid:304) tylko zaczeka(cid:254) na zako(cid:281)czenie dzia(cid:227)ania wszystkich w(cid:264)tków. for thread in threads: thread.join() end = time() print( Operacja zabra(cid:273)a .3f sekund (end - start)) Operacja zabra(cid:273)a 1.061 sekund Zaskakuj(cid:264)ce mo(cid:304)e by(cid:254), (cid:304)e równoleg(cid:227)e wykonywanie metody factorize() trwa(cid:227)o d(cid:227)u(cid:304)ej ni(cid:304) w przypadku jej szeregowego wywo(cid:227)ywania. Przeznaczaj(cid:264)c po jednym w(cid:264)tku dla ka(cid:304)dej liczby, w innych j(cid:269)zykach programowania mo(cid:304)na oczekiwa(cid:254) przy(cid:292)pieszenia dzia(cid:227)ania programu nieco mniejszego ni(cid:304) cztero- krotne, co wynika z obci(cid:264)(cid:304)enia zwi(cid:264)zanego z tworzeniem w(cid:264)tków i ich ko- ordynacj(cid:264). W przypadku komputera wyposa(cid:304)onego w procesor dwurdzeniowy mo(cid:304)na oczekiwa(cid:254) jedynie oko(cid:227)o dwukrotnego przy(cid:292)pieszenia wykonywania programu. Jednak nigdy nie b(cid:269)dziesz si(cid:269) spodziewa(cid:227), (cid:304)e wydajno(cid:292)(cid:254) b(cid:269)dzie gorsza, gdy do oblicze(cid:281) mo(cid:304)na wykorzysta(cid:254) wiele rdzeni procesora. To demon- struje wp(cid:227)yw mechanizmu GIL na programy wykonywane przez standar- dowy interpreter CPython. Istniej(cid:264) ró(cid:304)ne sposoby pozwalaj(cid:264)ce CPython na wykorzystanie wielu w(cid:264)tków, ale nie dzia(cid:227)aj(cid:264) one ze standardow(cid:264) klas(cid:264) Thread (patrz sposób 41.) i imple- mentacja tych rozwi(cid:264)za(cid:281) mo(cid:304)e wymaga(cid:254) do(cid:292)(cid:254) du(cid:304)ego wysi(cid:227)ku. Maj(cid:264)c (cid:292)wia- domo(cid:292)(cid:254) istnienia wspomnianych ogranicze(cid:281), mo(cid:304)esz si(cid:269) zastanawia(cid:254), dla- czego Python w ogóle obs(cid:227)uguje w(cid:264)tki. Mamy ku temu dwa dobre powody. Pierwszy — wiele w(cid:264)tków daje z(cid:227)udzenie, (cid:304)e program wykonuje jednocze- (cid:292)nie wiele zada(cid:281). Samodzielna implementacja mechanizmu jednoczesnego wykonywania zada(cid:281) jest trudna (przyk(cid:227)ad znajdziesz w sposobie 40.). Dzi(cid:269)ki w(cid:264)tkom pozostawiasz Pythonowi obs(cid:227)ug(cid:269) równoleg(cid:227)ego uruchamiania funkcji. Poleć książkęKup książkę 138 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) To dzia(cid:227)a, poniewa(cid:304) CPython gwarantuje zachowanie równo(cid:292)ci mi(cid:269)dzy uru- chomionymi w(cid:264)tkami Pythona, nawet je(cid:292)li ze wzgl(cid:269)du na ograniczenie na- k(cid:227)adane przez mechanizm GIL w danej chwili tylko jeden z nich robi post(cid:269)p. Drugi powód obs(cid:227)ugi w(cid:264)tków w Pythonie to blokuj(cid:264)ce operacje wej(cid:292)cia- -wyj(cid:292)cia, które zachodz(cid:264), gdy Python wykonuje okre(cid:292)lonego typu wywo(cid:227)ania systemowe. Za pomoc(cid:264) wspomnianych wywo(cid:227)a(cid:281) systemowych programy Pythona prosz(cid:264) system operacyjny komputera o interakcj(cid:269) ze (cid:292)rodowiskiem zewn(cid:269)trznym. Przyk(cid:227)ady blokuj(cid:264)cych operacji wej(cid:292)cia-wyj(cid:292)cia to odczyt i zapis plików, praca z sieciami, komunikacja z urz(cid:264)dzeniami takimi jak monitor itd. W(cid:264)tki pomagaj(cid:264) w obs(cid:227)udze blokuj(cid:264)cych operacji wej(cid:292)cia-wyj(cid:292)cia przez odizolowanie Twojego programu od czasu, jakiego system operacyjny potrze- buje na udzielenie odpowiedzi na (cid:304)(cid:264)dania. Za(cid:227)ó(cid:304)my, (cid:304)e za pomoc(cid:264) portu szeregowego chcesz wys(cid:227)a(cid:254) sygna(cid:227) do zdalnie sterowanego (cid:292)mig(cid:227)owca. Jako proxy dla tej czynno(cid:292)ci wykorzystamy wolne wywo(cid:227)anie systemowe (select). Funkcja prosi system operacyjny o blokad(cid:269) trwaj(cid:264)c(cid:264) 0,1 sekundy, a nast(cid:269)pnie zwraca kontrol(cid:269) z powrotem do programu. Otrzymujemy wi(cid:269)c sytuacj(cid:269) podobn(cid:264), jaka zachodzi podczas u(cid:304)ycia synchro- nicznego portu szeregowego. import select def slow_systemcall(): select.select([], [], [], 0.1) Szeregowe wykonywanie wywo(cid:227)a(cid:281) systemowych powoduje liniowe zwi(cid:269)k- szanie si(cid:269) ilo(cid:292)ci czasu niezb(cid:269)dnego do ich wykonania. start = time() for _ in range(5): slow_systemcall() end = time() print( Operacja zabra(cid:273)a .3f sekund (end - start)) Operacja zabra(cid:273)a 0.503 sekund Problem polega na tym, (cid:304)e w trakcie wykonywania funkcji slow_systemcall() program nie mo(cid:304)e zrobi(cid:254) (cid:304)adnego innego post(cid:269)pu. G(cid:227)ówny w(cid:264)tek programu zosta(cid:227) zablokowany przez wywo(cid:227)anie systemowe select. Tego rodzaju sytu- acja w praktyce jest straszna. Potrzebujesz sposobu pozwalaj(cid:264)cego na obli- czanie kolejnego ruchu (cid:292)mig(cid:227)owca podczas wysy(cid:227)ania sygna(cid:227)u, w przeciwnym razie (cid:292)mig(cid:227)owiec mo(cid:304)e si(cid:269) rozbi(cid:254). Kiedy wyst(cid:269)puje potrzeba jednoczesnego wykonania blokuj(cid:264)cych operacji wej(cid:292)cia-wyj(cid:292)cia i pewnych oblicze(cid:281), najwy(cid:304)- sza pora rozwa(cid:304)y(cid:254) przeniesienie wywo(cid:227)a(cid:281) systemowych do w(cid:264)tków. W poni(cid:304)szym fragmencie kodu mamy kilka wywo(cid:227)a(cid:281) funkcji slow_systemcall() w oddzielnych w(cid:264)tkach. To pozwoli na jednoczesn(cid:264) komunikacj(cid:269) z wieloma portami szeregowymi (i (cid:292)mig(cid:227)owcami), natomiast w(cid:264)tek g(cid:227)ówny b(cid:269)dzie po- zostawiony do wykonywania niezb(cid:269)dnych oblicze(cid:281). Poleć książkęKup książkę Sposób 37. U(cid:304)ycie w(cid:264)tków dla operacji blokuj(cid:264)cych wej(cid:292)cie-wyj(cid:292)cie 139 start = time() threads = [] for _ in range(5): thread = Thread(target=slow_systemcall) thread.start() threads.append(thread) Po uruchomieniu w(cid:264)tków mamy do wykonania pewn(cid:264) prac(cid:269), czyli oblicze- nie kolejnego ruchu (cid:292)mig(cid:227)owca przed oczekiwaniem na zako(cid:281)czenie dzia(cid:227)a- nia w(cid:264)tków obs(cid:227)uguj(cid:264)cych wywo(cid:227)ania systemowe. def compute_helicopter_location(index): # ... for i in range(5): compute_helicopter_location(i) for thread in threads: thread.join() end = time() print( Operacja zabra(cid:273)a .3f sekund (end - start)) Operacja zabra(cid:273)a 0.102 sekund Ca(cid:227)kowita ilo(cid:292)(cid:254) czasu potrzebnego na równoleg(cid:227)e wykonanie operacji jest pi(cid:269)ciokrotnie mniejsza ni(cid:304) w przypadku szeregowego wykonywania zada(cid:281). To pokazuje, (cid:304)e wywo(cid:227)ania systemowe s(cid:264) wykonywane równocze(cid:292)nie w wielu w(cid:264)tkach Pythona, nawet pomimo ogranicze(cid:281) nak(cid:227)adanych przez mechanizm GIL. Wprawdzie mechanizm GIL uniemo(cid:304)liwia równoleg(cid:227)e wykonywanie kodu utworzonego przez programist(cid:269), ale nie ma wp(cid:227)ywu ubocznego na wywo(cid:227)ania systemowe. Przedstawione rozwi(cid:264)zanie si(cid:269) sprawdza, poniewa(cid:304) w(cid:264)tki Pythona zwalniaj(cid:264) mechanizm GIL przed wykonaniem wywo(cid:227)a(cid:281) systemowych i ponow- nie do niego powracaj(cid:264) po zako(cid:281)czeniu wywo(cid:227)ania systemowego. Poza w(cid:264)tkami istnieje jeszcze wiele innych sposobów pracy z blokuj(cid:264)cymi operacjami wej(cid:292)cia-wyj(cid:292)cia, na przyk(cid:227)ad u(cid:304)ycie modu(cid:227)u asyncio. Wspomniane rozwi(cid:264)zania alternatywne przynosz(cid:264) wa(cid:304)ne korzy(cid:292)ci. Jednak wymagaj(cid:264) tak(cid:304)e dodatkowej pracy w postaci konieczno(cid:292)ci refaktoryzacji kodu (cid:302)ród(cid:227)owego, aby go dopasowa(cid:254) do innego modelu wykonywania (patrz sposób 40.). U(cid:304)ycie w(cid:264)tków to najprostszy sposób na równoleg(cid:227)e wykonywanie blokuj(cid:264)cych ope- racji wej(cid:292)cia-wyj(cid:292)cia i jednocze(cid:292)nie wymaga wprowadzania jedynie mini- malnych zmian w programie. Do zapami(cid:269)tania (cid:141) Z powodu dzia(cid:227)ania globalnej blokady interpretera (mechanizm GIL) w(cid:264)tki Pythona nie pozwalaj(cid:264) na równoleg(cid:227)e uruchamianie kodu bajtowe- go w wielu rdzeniach procesora. (cid:141) Pomimo istnienia mechanizmu GIL w(cid:264)tki Pythona nadal pozostaj(cid:264) u(cid:304)yteczne, poniewa(cid:304) oferuj(cid:264) (cid:227)atwy sposób jednoczesnego wykonywania wielu zada(cid:281). Poleć książkęKup książkę 140 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) (cid:141) U(cid:304)ywaj w(cid:264)tków Pythona do równoczesnego wykonywania wielu wywo(cid:227)a(cid:281) systemowych. Tym samym b(cid:269)dzie mo(cid:304)na jednocze(cid:292)nie wykonywa(cid:254) blo- kuj(cid:264)ce operacje wej(cid:292)cia-wyj(cid:292)cia oraz pewne obliczenia. Sposób 38. U(cid:304)ywaj klasy Lock, aby unika(cid:254) stanu wy(cid:292)cigu w w(cid:264)tkach Po dowiedzeniu si(cid:269) o istnieniu mechanizmu GIL (patrz sposób 37.) wielu nowych programistów Pythona przyjmuje za(cid:227)o(cid:304)enie, (cid:304)e mo(cid:304)na zrezygnowa(cid:254) z u(cid:304)ycia muteksu w kodzie. Skoro mechanizm GIL uniemo(cid:304)liwia w(cid:264)tkom Pythona ich równoczesne dzia(cid:227)anie w wielu rdzeniach procesora, wi(cid:269)c mo(cid:304)na wysnu(cid:254) wniosek, (cid:304)e ta sama blokada musi dotyczy(cid:254) tak(cid:304)e struktur danych programu, prawda? Pewne testy przeprowadzone na typach takich jak listy i s(cid:227)owniki mog(cid:264) nawet pokaza(cid:254), (cid:304)e przyj(cid:269)te za(cid:227)o(cid:304)enie jest s(cid:227)uszne. Musisz mie(cid:254) jednak (cid:292)wiadomo(cid:292)(cid:254), (cid:304)e niekoniecznie tak jest. Mechanizm GIL nie zapewnia ochrony programowi. Wprawdzie w danej chwili jest wykony- wany tylko jeden w(cid:264)tek Pythona, ale operacje w(cid:264)tku na strukturach danych mog(cid:264) by(cid:254) zak(cid:227)ócone mi(cid:269)dzy dwoma instrukcjami kodu bajtowego w interpre- terze Pythona. To jest niebezpieczne, je(cid:292)li jednocze(cid:292)nie z wielu w(cid:264)tków pró- bujesz uzyska(cid:254) dost(cid:269)p do tych samych obiektów. Struktury danych mog(cid:264) by(cid:254) praktycznie w ka(cid:304)dej chwili uszkodzone na skutek wspomnianych zak(cid:227)ó- ce(cid:281), co doprowadzi do uszkodzenia programu. Za(cid:227)ó(cid:304)my, (cid:304)e tworzysz program przeprowadzaj(cid:264)cy równocze(cid:292)nie wiele opera- cji, takich jak sprawdzanie poziomu (cid:292)wiat(cid:227)a w pewnej liczbie czujników sieciowych. Je(cid:304)eli chcesz okre(cid:292)li(cid:254) ca(cid:227)kowit(cid:264) liczb(cid:269) próbek, jakie mia(cid:227)y miej- sce w danym czasie, mo(cid:304)esz je agregowa(cid:254) za pomoc(cid:264) nowej klasy. class Counter(object): def __init__(self): self.count = 0 def increment(self, offset): self.count += offset Wyobra(cid:302) sobie, (cid:304)e ka(cid:304)dy czujnik ma w(cid:227)asny w(cid:264)tek roboczy, poniewa(cid:304) odczyt czujnika wymaga blokuj(cid:264)cej operacji wej(cid:292)cia-wyj(cid:292)cia. Po przeprowadzeniu pomiaru w(cid:264)tek roboczy inkrementuje warto(cid:292)(cid:254) licznika, cykl jest powtarzany a(cid:304) do osi(cid:264)gni(cid:269)cia maksymalnej liczby oczekiwanych operacji odczytu. def worker(sensor_index, how_many, counter): for _ in range(how_many): # Odczyt danych z czujnika. # ... counter.increment(1) Poleć książkęKup książkę Sposób 38. U(cid:304)ywaj klasy Lock, aby unika(cid:254) stanu wy(cid:292)cigu w w(cid:264)tkach 141 Poni(cid:304)ej przedstawi(cid:227)em definicj(cid:269) funkcji uruchamiaj(cid:264)cej w(cid:264)tek roboczy dla poszczególnych czujników oraz oczekuj(cid:264)cej na zako(cid:281)czenie odczytu przez ka(cid:304)dy z nich: def run_threads(func, how_many, counter): threads = [] for i in range(5): args = (i, how_many, counter) thread = Thread(target=func, args=args) threads.append(thread) thread.start() for thread in threads: thread.join() Jednoczesne uruchomienie pi(cid:269)ciu w(cid:264)tków wydaje si(cid:269) proste, a dane wyj- (cid:292)ciowe powinny by(cid:254) oczywiste. how_many = 10**5 counter = Counter() run_threads(worker, how_many, counter) print( Oczekiwana liczba próbek d, znaleziona d (5 * how_many, counter.count)) Oczekiwana liczba próbek 500000, znaleziona 278328 Jednak wynik znacznie odbiega od oczekiwanego! Co si(cid:269) sta(cid:227)o? Jak co(cid:292) tak prostego mog(cid:227)o si(cid:269) nie uda(cid:254), zw(cid:227)aszcza (cid:304)e w danej chwili mo(cid:304)e dzia(cid:227)a(cid:254) tylko jeden w(cid:264)tek interpretera Pythona? Interpreter Pythona wymusza zachowanie sprawiedliwo(cid:292)ci mi(cid:269)dzy wyko- nywanymi w(cid:264)tkami, aby wszystkie otrzyma(cid:227)y praktycznie tak(cid:264) sam(cid:264) ilo(cid:292)(cid:254) czasu procesora. Dlatego te(cid:304) Python b(cid:269)dzie wstrzymywa(cid:254) dzia(cid:227)anie bie(cid:304)(cid:264)- cego w(cid:264)tku i wznawia(cid:254) dzia(cid:227)anie kolejnego. Problem polega na tym, (cid:304)e do- k(cid:227)adnie nie wiesz, kiedy Python wstrzyma dzia(cid:227)anie Twoich w(cid:264)tków. W(cid:264)tek mo(cid:304)e by(cid:254) wi(cid:269)c wstrzymany nawet w po(cid:227)owie operacji, która powinna pozosta(cid:254) niepodzielna. Tak si(cid:269) w(cid:227)a(cid:292)nie sta(cid:227)o w omawianym przyk(cid:227)adzie. Metoda increment() obiektu Counter wygl(cid:264)da na prost(cid:264). counter.count += offset Jednak operator += u(cid:304)yty w atrybucie obiektu tak naprawd(cid:269) nakazuje Pytho- nowi wykonanie w tle trzech oddzielnych operacji. Powy(cid:304)sze polecenie jest odpowiednikiem trzech poni(cid:304)szych: value = getattr(counter, count ) result = value + offset setattr(counter, count , result) W(cid:264)tki Pythona przeprowadzaj(cid:264)ce inkrementacj(cid:269) mog(cid:264) zosta(cid:254) wstrzymane mi(cid:269)dzy dwoma dowolnymi operacjami przedstawionymi powy(cid:304)ej. To b(cid:269)dzie problematyczne, je(cid:292)li stara wersja value zostanie przypisana licznikowi. Oto przyk(cid:227)ad nieprawid(cid:227)owej interakcji mi(cid:269)dzy dwoma w(cid:264)tkami A i B: Poleć książkęKup książkę 142 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) # Wykonywanie wątku A. value_a = getattr(counter, count ) # Przełączenie kontekstu do wątku B. value_b = getattr(counter, count ) result_b = value_b + 1 setattr(counter, count , result_b) # Przełączenie kontekstu z powrotem do wątku A. result_a = value_a + 1 setattr(counter, count , result_a) Po prze(cid:227)(cid:264)czeniu kontekstu z w(cid:264)tku A do B nast(cid:264)pi(cid:227)o usuni(cid:269)cie ca(cid:227)ego po- st(cid:269)pu w trakcie operacji inkrementacji licznika. Dok(cid:227)adnie to zdarzy(cid:227)o si(cid:269) w przedstawionym powy(cid:304)ej przyk(cid:227)adzie obs(cid:227)ugi czujników (cid:292)wiat(cid:227)a. Aby zapobiec tego rodzaju sytuacji wy(cid:292)cigu do danych oraz innym formom uszkodzenia struktur danych, Python zawiera solidny zestaw narz(cid:269)dzi do- st(cid:269)pnych we wbudowanym module threading. Najprostsze i naju(cid:304)yteczniej- sze z nich to klasa Lock zapewniaj(cid:264)ca obs(cid:227)ug(cid:269) muteksu. Dzi(cid:269)ki zastosowaniu blokady klasa Counter mo(cid:304)e chroni(cid:254) jej warto(cid:292)(cid:254) bie(cid:304)(cid:264)c(cid:264) przed jednoczesnym dost(cid:269)pem z wielu w(cid:264)tków. W danej chwili tylko jeden w(cid:264)tek b(cid:269)dzie mia(cid:227) mo(cid:304)liwo(cid:292)(cid:254) na(cid:227)o(cid:304)enia blokady. W poni(cid:304)szym fragmencie kodu u(cid:304)y(cid:227)em polecenia with do na(cid:227)o(cid:304)enia i zwolnienia blokady. To znacznie u(cid:227)atwia ustalenie, który kod jest wykonywany w trakcie trwania blokady (wi(cid:269)cej informacji szczegó(cid:227)owych na ten temat znajdziesz w sposobie 43.). class LockingCounter(object): def __init__(self): self.lock = Lock() self.count = 0 def increment(self, offset): with self.lock: self.count += offset Teraz podobnie jak wcze(cid:292)niej uruchamiam w(cid:264)tki robocze, ale w tym celu u(cid:304)ywam wywo(cid:227)ania LockingCounter(). counter = LockingCounter() run_threads(worker, how_many, counter) print( Oczekiwana liczba próbek d, znaleziona d (5 * how_many, counter.count)) Oczekiwana liczba próbek 500000, znaleziona 500000 Otrzymany wynik dok(cid:227)adnie pokrywa si(cid:269) z oczekiwanym. Klasa Lock pozwo- li(cid:227)a na rozwi(cid:264)zanie problemu. Do zapami(cid:269)tania (cid:141) Cho(cid:254) Python ma mechanizm GIL, nadal pozostajesz odpowiedzialny za uni- kanie powstawania sytuacji wy(cid:292)cigu do danych mi(cid:269)dzy w(cid:264)tkami u(cid:304)ywany- mi przez Twój program. Poleć książkęKup książkę Sposób 39. U(cid:304)ywaj klasy Queue do koordynacji pracy mi(cid:269)dzy w(cid:264)tkami 143 (cid:141) Twoje programy mog(cid:264) uszkodzi(cid:254) stosowane w nich struktury danych, je(cid:292)li pozwolisz, aby wiele w(cid:264)tków jednocze(cid:292)nie modyfikowa(cid:227)o te same obiekty bez nak(cid:227)adania na nie blokad. (cid:141) Klasa Lock oferowana przez wbudowany modu(cid:227) threading to standardowa implementacja mutekstu w Pythonie. Sposób 39. U(cid:304)ywaj klasy Queue do koordynacji pracy mi(cid:269)dzy w(cid:264)tkami Programy Pythona równocze(cid:292)nie wykonuj(cid:264)ce wiele zada(cid:281) cz(cid:269)sto musz(cid:264) ko- ordynowa(cid:254) t(cid:269) prac(cid:269). Jednym z naju(cid:304)yteczniejszych narz(cid:269)dzi przeznaczo- nych do koordynacji jednocze(cid:292)nie wykonywanych zada(cid:281) jest potokowanie funkcji. Potokowanie dzia(cid:227)a na zasadzie podobnej do linii monta(cid:304)owej w przedsi(cid:269)- biorstwie. Potoki maj(cid:264) wiele faz w serii wraz z okre(cid:292)lonymi funkcjami dla poszczególnych faz. Nowe zadania do wykonania s(cid:264) nieustannie umieszczane na pocz(cid:264)tku potoku. Wszystkie funkcje mog(cid:264) równolegle pracowa(cid:254) nad zada- niami w obs(cid:227)ugiwanych przez nie fazach. Ca(cid:227)a praca przesuwa si(cid:269) do przodu, gdy wszystkie funkcje zako(cid:281)cz(cid:264) swoje zadanie. Cykl trwa a(cid:304) do wykonania wszystkich faz. Tego rodzaju podej(cid:292)cie jest szczególnie dobre w przypadku pracy wymagaj(cid:264)cej u(cid:304)ycia blokuj(cid:264)cych operacji wej(cid:292)cia-wyj(cid:292)cia lub podproce- sów — czyli w przypadku zada(cid:281), które mog(cid:264) by(cid:254) (cid:227)atwo wykonywane rów- nolegle za pomoc(cid:264) Pythona (patrz sposób 37.). Na przyk(cid:227)ad chcesz zbudowa(cid:254) system, który b(cid:269)dzie pobiera(cid:227) sta(cid:227)y strumie(cid:281) zdj(cid:269)(cid:254) z aparatu cyfrowego, zmienia(cid:227) ich wielko(cid:292)(cid:254), a nast(cid:269)pnie przekazywa(cid:227) zdj(cid:269)cia do galerii w internecie. Tego rodzaju program mo(cid:304)na podzieli(cid:254) na trzy fazy potoku. W pierwszej fazie b(cid:269)d(cid:264) pobierane nowe zdj(cid:269)cia z aparatu. W drugiej fazie pobrane zdj(cid:269)cia zostan(cid:264) przetworzone przez funkcj(cid:269) odpo- wiedzialn(cid:264) za zmian(cid:269) ich wielko(cid:292)ci. Nast(cid:269)pnie w trzeciej i ostatniej fazie zmo- dyfikowane zdj(cid:269)cia b(cid:269)d(cid:264) za pomoc(cid:264) odpowiedniej funkcji przekazane do galerii internetowej. Wyobra(cid:302) sobie, (cid:304)e ju(cid:304) utworzy(cid:227)e(cid:292) funkcje Pythona przeznaczone do wyko- nywania poszczególnych faz: download(), resize() i upload(). W jaki sposób mo(cid:304)- na przygotowa(cid:254) potok, aby praca mog(cid:227)a by(cid:254) prowadzona równocze(cid:292)nie? Przede wszystkim potrzebny jest sposób umo(cid:304)liwiaj(cid:264)cy przekazywanie pra- cy mi(cid:269)dzy poszczególnymi fazami potoku. Do tego celu mo(cid:304)na wykorzysta(cid:254) zapewniaj(cid:264)c(cid:264) bezpiecze(cid:281)stwo w(cid:264)tków kolejk(cid:269) producent-konsument. (Za- poznaj si(cid:269) ze sposobem 38., aby zrozumie(cid:254) wag(cid:269) bezpiecze(cid:281)stwa w(cid:264)tków w Pythonie. Z kolei w sposobie 46. znajdziesz wi(cid:269)cej informacji o klasie deque). Poleć książkęKup książkę 144 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) class MyQueue(object): def __init__(self): self.items = deque() self.lock = Lock() Producent, czyli w omawianym przyk(cid:227)adzie aparat cyfrowy, umieszcza no- we zdj(cid:269)cia na ko(cid:281)cu listy oczekuj(cid:264)cych elementów. def put(self, item): with self.lock: self.items.append(item) Konsument, czyli w omawianym przyk(cid:227)adzie pierwsza faza potoku przetwa- rzania, usuwa zdj(cid:269)cia z pocz(cid:264)tku listy oczekuj(cid:264)cych elementów. def get(self): with self.lock: return self.items.popleft() Poni(cid:304)ej poszczególne fazy potoku przedstawi(cid:227)em jako w(cid:264)tek Pythona, który pobiera prac(cid:269) z kolejki, takiej jak wcze(cid:292)niej wspomniana, wykonuje odpo- wiedni(cid:264) funkcj(cid:269), a nast(cid:269)pnie uzyskany wynik umieszcza w innej kolejce. Ponadto monitoruje liczb(cid:269) razy, jakie w(cid:264)tek roboczy zosta(cid:227) sprawdzony pod k(cid:264)tem nowych danych wej(cid:292)ciowych oraz ilo(cid:292)(cid:254) wykonanej pracy. class Worker(Thread): def __init__(self, func, in_queue, out_queue): super().__init__() self.func = func self.in_queue = in_queue self.out_queue = out_queue self.polled_count = 0 self.work_done = 0 Najtrudniejsza cz(cid:269)(cid:292)(cid:254) wi(cid:264)(cid:304)e si(cid:269) z tym, (cid:304)e w(cid:264)tek roboczy musi prawid(cid:227)owo ob- s(cid:227)u(cid:304)y(cid:254) sytuacj(cid:269), w której kolejka danych wej(cid:292)ciowych b(cid:269)dzie pusta, ponie- wa(cid:304) poprzednia faza jeszcze nie zako(cid:281)czy(cid:227)a swojego zadania. Tym zajmujemy si(cid:269) tam, gdzie nast(cid:269)puje zg(cid:227)oszenie wyj(cid:264)tku IndexError. Mo(cid:304)na to potraktowa(cid:254) jako przestój na linii monta(cid:304)owej. def run(self): while True: self.polled_count += 1 try: item = self.in_queue.get() except IndexError: sleep(0.01) # Brak zadania do wykonania. else: result = self.func(item) self.out_queue.put(result) self.work_done += 1 Teraz pozosta(cid:227)o ju(cid:304) po(cid:227)(cid:264)czenie trzech wymienionych faz ze sob(cid:264) przez utworzenie kolejek przeznaczonych do koordynacji oraz odpowiednich w(cid:264)t- ków roboczych. Poleć książkęKup książkę Sposób 39. U(cid:304)ywaj klasy Queue do koordynacji pracy mi(cid:269)dzy w(cid:264)tkami 145 download_queue = MyQueue() resize_queue = MyQueue() upload_queue = MyQueue() done_queue = MyQueue() threads = [ Worker(download, download_queue, resize_queue), Worker(resize, resize_queue, upload_queue), Worker(upload, upload_queue, done_queue), ] Mo(cid:304)na uruchomi(cid:254) w(cid:264)tki, a nast(cid:269)pnie wstrzykn(cid:264)(cid:254) pewn(cid:264) ilo(cid:292)(cid:254) pracy do pierw- szej fazy potoku. W poni(cid:304)szym fragmencie kodu jako proxy dla rzeczywi- stych danych wymaganych przez funkcj(cid:269) download() wykorzysta(cid:227)em zwyk(cid:227)y egzemplarz object. for thread in threads: thread.start() for _ in range(1000): download_queue.put(object()) Pozosta(cid:227)o ju(cid:304) zaczeka(cid:254) do chwili, gdy wszystkie elementy zostan(cid:264) przetwo- rzone przez potok i znajd(cid:264) si(cid:269) w kolejce done_queue. while len(done_queue.items) 1000: # Zrób coś użytecznego podczas oczekiwania. # ... Rozwi(cid:264)zanie dzia(cid:227)a prawid(cid:227)owo, ale wyst(cid:269)puje interesuj(cid:264)cy efekt uboczny spowodowany przez w(cid:264)tki sprawdzaj(cid:264)ce ich kolejki danych wej(cid:292)ciowych pod k(cid:264)tem nowych zada(cid:281) do wykonania. Najtrudniejsza cz(cid:269)(cid:292)(cid:254) podczas prze- chwytywania wyj(cid:264)tków IndexError w metodzie run() jest wykonywana bardzo du(cid:304)(cid:264) liczb(cid:269) razy. processed = len(done_queue.items) polled = sum(t.polled_count for t in threads) print( Prztworzono , processed, elementów po wykonaniu , polled, sprawdze(cid:275) ) Przetworzono 1000 elementów po wykonaniu 3030 sprawdze(cid:275) Szybko(cid:292)(cid:254) dzia(cid:227)ania poszczególnych funkcji roboczych mo(cid:304)e by(cid:254) ró(cid:304)na, a wi(cid:269)c wcze(cid:292)niejsza faza mo(cid:304)e uniemo(cid:304)liwi(cid:254) dokonanie post(cid:269)pu w pó(cid:302)niejszych fa- zach, tym samym korkuj(cid:264)c potok. To powoduje, (cid:304)e pó(cid:302)niejsze fazy s(cid:264) wstrzy- mane i nieustannie sprawdzaj(cid:264) ich kolejki danych wej(cid:292)ciowych pod k(cid:264)tem nowych zada(cid:281) do wykonania. Skutkiem b(cid:269)dzie marnowanie przez w(cid:264)tki robocze czasu procesora na wykonywanie nieu(cid:304)ytecznych zada(cid:281) (b(cid:269)d(cid:264) ci(cid:264)- gle zg(cid:227)asza(cid:254) i przechwytywa(cid:254) wyj(cid:264)tki IndexError). To jednak dopiero pocz(cid:264)tek nieodpowiednich dzia(cid:227)a(cid:281) podejmowanych przez t(cid:269) implementacj(cid:269). Wyst(cid:269)puj(cid:264) w niej jeszcze trzy kolejne b(cid:227)(cid:269)dy, których równie(cid:304) nale(cid:304)y unika(cid:254). Po pierwsze, operacja okre(cid:292)lenia, czy wszystkie dane wej(cid:292)ciowe zosta(cid:227)y przetworzone, wymaga oczekiwania w kolejce done_queue. Po drugie, Poleć książkęKup książkę 146 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) w klasie Worker metoda run() b(cid:269)dzie wykonywana w niesko(cid:281)czono(cid:292)(cid:254) w p(cid:269)tli. Nie ma mo(cid:304)liwo(cid:292)ci wskazania w(cid:264)tkowi roboczemu, (cid:304)e czas zako(cid:281)czy(cid:254) dzia(cid:227)anie. Po trzecie (to najpowa(cid:304)niejszy w skutkach z b(cid:227)(cid:269)dów), zatkanie potoku mo(cid:304)e doprowadzi(cid:254) do awarii programu. Je(cid:304)eli w fazie pierwszej nast(cid:264)pi du(cid:304)y po- st(cid:269)p, natomiast w fazie drugiej du(cid:304)e spowolnienie, to kolejka (cid:227)(cid:264)cz(cid:264)ca obie fazy b(cid:269)dzie si(cid:269) nieustannie zwi(cid:269)ksza(cid:254). Druga faza po prostu nie b(cid:269)dzie w sta- nie nad(cid:264)(cid:304)y(cid:254) za pierwsz(cid:264) z wykonywaniem swojej pracy. Przy wystarczaj(cid:264)co du(cid:304)ej ilo(cid:292)ci czasu i danych wej(cid:292)ciowych skutkiem b(cid:269)dzie zu(cid:304)ycie przez pro- gram ca(cid:227)ej wolnej pami(cid:269)ci, a nast(cid:269)pnie awaria aplikacji. Mo(cid:304)na wi(cid:269)c wyci(cid:264)gn(cid:264)(cid:254) wniosek, (cid:304)e potoki s(cid:264) z(cid:227)ym rozwi(cid:264)zaniem. Trudno samodzielnie zbudowa(cid:254) dobr(cid:264) kolejk(cid:269) producent-konsument. Ratunek w postaci klasy Queue Klasa Queue z wbudowanego modu(cid:227)u queue dostarcza ca(cid:227)(cid:264) funkcjonalno(cid:292)(cid:254), której potrzebujemy do rozwi(cid:264)zania przedstawionych wcze(cid:292)niej problemów. Klasa Queue eliminuje oczekiwanie w w(cid:264)tku roboczym, poniewa(cid:304) metoda get() jest zablokowana a(cid:304) do chwili udost(cid:269)pnienia nowych danych. Na przyk(cid:227)ad po- ni(cid:304)ej przedstawi(cid:227)em kod uruchamiaj(cid:264)cy w(cid:264)tek, który oczekuje na pojawie- nie si(cid:269) w kolejce pewnych danych wej(cid:292)ciowych. from queue import Queue queue = Queue() def consumer(): print( Konsument oczekuje ) queue.get() # Uruchomienie po metodzie put() przedstawionej poniżej. print( Konsument zako(cid:275)czy(cid:273) prac(cid:250) ) thread = Thread(target=consumer) thread.start() Wprawdzie w(cid:264)tek jest uruchomiony jako pierwszy, ale nie zako(cid:281)czy dzia(cid:227)a- nia a(cid:304) do chwili umieszczenia elementu w egzemplarzu Queue, gdy metoda get() b(cid:269)dzie mia(cid:227)a jakiekolwiek dane do przekazania. print( Producent umieszcza dane ) queue.put(object()) # Uruchomienie przed metodą get() przedstawioną powyżej. thread.join() print( Producent zako(cid:275)czy(cid:273) prac(cid:250) ) Konsument oczekuje Producent umieszcza dane Konsument zako(cid:275)czy(cid:273) prac(cid:250) Producent zako(cid:275)czy(cid:273) prac(cid:250) W celu rozwi(cid:264)zania problemu z zatykaniem potoku, klasa Queue pozwala na podanie maksymalnej liczby zada(cid:281), jakie mog(cid:264) mi(cid:269)dzy dwoma fazami oczeki- wa(cid:254) na wykonanie. Bufor ten powoduje wywo(cid:227)anie metody put() w celu na(cid:227)o- Poleć książkęKup książkę Sposób 39. U(cid:304)ywaj klasy Queue do koordynacji pracy mi(cid:269)dzy w(cid:264)tkami 147 (cid:304)enia blokady, gdy kolejka jest ju(cid:304) zape(cid:227)niona. W poni(cid:304)szym fragmencie kodu przedstawi(cid:227)em definicj(cid:269) w(cid:264)tku oczekuj(cid:264)cego chwil(cid:269) przed u(cid:304)yciem kolejki: queue = Queue(1) # Bufor o wielkości 1. def consumer(): time.sleep(0.1) # Oczekiwanie. queue.get() # Drugie wywołanie. print( Konsument pobiera dane 1 ) queue.get() # Czwarte wywołanie. print( Konsument pobiera dane 2 ) thread = Thread(target=consumer) thread.start() Oczekiwanie powinno pozwoli(cid:254) w(cid:264)tkowi producenta na umieszczenie obu obiektów w kolejce, zanim w(cid:264)tek konsumenta w ogóle wywo(cid:227)a metod(cid:269) get(). Jednak wielko(cid:292)(cid:254) Queue wynosi 1. To oznacza, (cid:304)e producent dodaj(cid:264)cy elementy do kolejki b(cid:269)dzie musia(cid:227) zaczeka(cid:254), a(cid:304) w(cid:264)tek konsumenta przynajmniej raz wywo(cid:227)a metod(cid:269) get(). Dopiero wtedy drugie wywo(cid:227)anie put() zwolni blokad(cid:269) i pozwoli na dodanie drugiego elementu do kolejki. queue.put(object()) # Pierwsze wywołanie. print( Producent umieszcza dane 1 ) queue.put(object()) # Trzecie wywołanie. print( Producent umieszcza dane 2 ) thread.join() print( Producent zako(cid:275)czy(cid:273) prac(cid:250) ) Producent umieszcza dane 1 Konsument pobiera dane 1 Producent umieszcza dane 2 Konsument pobiera dane 2 Producent zako(cid:275)czy(cid:273) prac(cid:250) Klasa Queue mo(cid:304)e równie(cid:304) monitorowa(cid:254) post(cid:269)p pracy, u(cid:304)ywaj(cid:264)c do tego metody task_done(). W ten sposób mo(cid:304)na zaczeka(cid:254), a(cid:304) kolejka danych wej(cid:292)ciowych fazy zostanie opró(cid:304)niona, co eliminuje konieczno(cid:292)(cid:254) sprawdzania kolejki done_queue na ko(cid:281)cu potoku. Na przyk(cid:227)ad poni(cid:304)ej zdefiniowa(cid:227)em w(cid:264)tek konsumenta wywo(cid:227)uj(cid:264)cy metod(cid:269) task_done() po zako(cid:281)czeniu pracy nad elementem. in_queue = Queue() def consumer(): print( Konsument oczekuje ) work = in_queue.get() # Zakończone jako drugie. print( Konsument pracuje ) # Wykonywanie pracy. # ... print( Konsument zako(cid:275)czy(cid:273) prac(cid:250) ) in_queue.task_done() # Zakończone jako trzecie. Thread(target=consumer).start() Poleć książkęKup książkę 148 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) Teraz kod producenta nie musi (cid:227)(cid:264)czy(cid:254) si(cid:269) z w(cid:264)tkiem konsumenta lub spraw- dza(cid:254) go. Producent mo(cid:304)e po prostu poczeka(cid:254) na zako(cid:281)czenie pracy przez kolejk(cid:269) in_queue, wywo(cid:227)uj(cid:264)c metod(cid:269) join() w egzemplarzu Queue. Nawet je(cid:292)li kolejka in_queue jest pusta, to nie b(cid:269)dzie mo(cid:304)na si(cid:269) do niej przy(cid:227)(cid:264)czy(cid:254), do- póki nie zostanie wywo(cid:227)ana metoda task_done() dla ka(cid:304)dego elementu, który kiedykolwiek by(cid:227) kolejkowany. in_queue.put(object()) # Zakończone jako pierwsze. print( Producent oczekuje ) in_queue.join() # Zakończone jako czwarte. print( Producent zako(cid:275)czy(cid:273) prac(cid:250) ) Konsument oczekuje Producent oczekuje Konsument pracuje Konsument zako(cid:275)czy(cid:273) prac(cid:250) Producent zako(cid:275)czy(cid:273) prac(cid:250) Wszystkie wymienione funkcje mo(cid:304)na umie(cid:292)ci(cid:254) razem w podklasie klasy Queue, która równie(cid:304) poinformuje w(cid:264)tek roboczy o konieczno(cid:292)ci zako(cid:281)czenia przetwarzania. W poni(cid:304)szym fragmencie kodu znajduje si(cid:269) zdefiniowana metoda close() dodaj(cid:264)ca do kolejki element specjalny, który wskazuje, (cid:304)e po nim nie powinny znajdowa(cid:254) si(cid:269) ju(cid:304) (cid:304)adne elementy danych wej(cid:292)ciowych: class ClosableQueue(Queue): SENTINEL = object() def close(self): self.put(self.SENTINEL) Nast(cid:269)pnie definiujemy iterator dla kolejki, który wyszukuje wspomniany element specjalny i zatrzymuje iteracj(cid:269) po znalezieniu tego elementu. Metoda iteratora __iter__() powoduje równie(cid:304) wywo(cid:227)anie metody task_done() w odpo- wiednim momencie, co pozwala na monitorowanie post(cid:269)pu pracy w kolejce. def __iter__(self): while True: item = self.get() try: if item is self.SENTINEL: return # Powoduje zakończenie działania wątku. yield item finally: self.task_done() Teraz mo(cid:304)na przedefiniowa(cid:254) w(cid:264)tek roboczy, aby opiera(cid:227) si(cid:269) na funkcjonal- no(cid:292)ci dostarczanej przez klas(cid:269) ClosableQueue. W(cid:264)tek zako(cid:281)czy dzia(cid:227)anie po zako(cid:281)czeniu p(cid:269)tli. class StoppableWorker(Thread): def __init__(self, func, in_queue, out_queue): # ... Poleć książkęKup książkę Sposób 39. U(cid:304)ywaj klasy Queue do koordynacji pracy mi(cid:269)dzy w(cid:264)tkami 149 def run(self): for item in self.in_queue: result = self.func(item) self.out_queue.put(result) Poni(cid:304)ej przedstawi(cid:227)em kod odpowiedzialny za utworzenie zbioru w(cid:264)tków roboczych na podstawie nowej klasy: download_queue = ClosableQueue() # ... threads = [ StoppableWorker(download, download_queue, resize_queue), # ... ] Po uruchomieniu w(cid:264)tków roboczych sygna(cid:227) zatrzymania podobnie jak wcze- (cid:292)niej jest wysy(cid:227)any przez zamkni(cid:269)cie kolejki danych wej(cid:292)ciowych dla pierw- szej fazy po umieszczeniu w niej wszystkich elementów. for thread in threads: thread.start() for _ in range(1000): download_queue.put(object()) download_queue.close() Pozosta(cid:227)o ju(cid:304) tylko oczekiwanie na zako(cid:281)czenie pracy przez po(cid:227)(cid:264)czenie po- szczególnych kolejek znajduj(cid:264)cych si(cid:269) mi(cid:269)dzy fazami. Gdy dana faza zo- stanie zako(cid:281)czona, to jest to sygnalizowane kolejnej fazie przez zamkni(cid:269)cie jej kolejki danych wej(cid:292)ciowych. Na ko(cid:281)cu kolejka done_queue zawiera zgodnie z oczekiwaniami wszystkie obiekty danych wyj(cid:292)ciowych. download_queue.join() resize_queue.close() resize_queue.join() upload_queue.close() upload_queue.join() print(done_queue.qsize(), elementów zosta(cid:273)o przetworzonych ) 1000 elementów zosta(cid:273)o przetworzonych Do zapami(cid:269)tania (cid:141) Potoki to doskona(cid:227)y sposób organizowania sekwencji zada(cid:281) jednocze(cid:292)nie wykonywanych przez wiele w(cid:264)tków Pythona. (cid:141) Musisz by(cid:254) (cid:292)wiadom, (cid:304)e podczas tworzenia potoków, które jednocze(cid:292)nie wykonuj(cid:264) wiele zada(cid:281), pojawiaj(cid:264) si(cid:269) problemy: oczekiwanie blokuj(cid:264)ce do- st(cid:269)p, zatrzymywanie w(cid:264)tków roboczych i niebezpiecze(cid:281)stwo zu(cid:304)ycia ca(cid:227)ej dost(cid:269)pnej pami(cid:269)ci. (cid:141) Klasa Queue oferuje ca(cid:227)(cid:264) funkcjonalno(cid:292)(cid:254), jakiej potrzebujesz do przygoto- wania niezawodnych potoków: obs(cid:227)ug(cid:269) blokad, bufory o wskazanej wiel- ko(cid:292)ci i do(cid:227)(cid:264)czanie do kolejek. Poleć książkęKup książkę 150 Rozdzia(cid:227) 5. Wspó(cid:227)bie(cid:304)no(cid:292)(cid:254) i równoleg(cid:227)o(cid:292)(cid:254) Sposób 40. Rozwa(cid:304) u(cid:304)ycie wspó(cid:227)programów Sposób 40. U(cid:304)ycie wspó(cid:227)programów w celu jednoczesnego wykonywania wielu funkcji w celu jednoczesnego wykonywania wielu funkcji W(cid:264)tki umo(cid:304)liwiaj(cid:264) programistom Pythona pozornie jednoczesne wykony-
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Efektywny Python. 59 sposobów na lepszy kod
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: