język c++ i przetwarzanie współbieżne w akcji · spis treci sowo wstpne ..... .....11

64

Upload: hoangkhanh

Post on 27-Sep-2018

214 views

Category:

Documents


0 download

TRANSCRIPT

Tytuł oryginału: C++ Concurrency in Action: Practical Multithreading

Tłumaczenie: Mikołaj Szczepaniak

Projekt okładki: Anna MitkaMateriały graficzne na okładce zostały wykorzystane za zgodą Shutterstock Images LLC.

ISBN: 978-83-246-5086-6

Original edition copyright © 2012 by Manning Publications Co.All rights reserved.

Polish edition copyright © 2013 by HELION SA.All rights reserved.

All rights reserved. No part of this book may be 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 the Publisher.

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 HELIONul. Kościuszki 1c, 44-100 GLIWICEtel. 32 231 22 19, 32 230 98 63e-mail: [email protected]: 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?gimpbiMoż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�ciS�owo wst�pne .......................................................................................................................................... 11Podzi�kowania .......................................................................................................................................... 13O tej ksi��ce .............................................................................................................................................. 15

Rozdzia� 1. Witaj, �wiecie wspó�bie�no�ci w C++! 191.1. Czym jest wspó�bie�no��? ........................................................................................................... 20

1.1.1. Wspó�bie�no�� w systemach komputerowych ................................................................. 201.1.2. Modele wspó�bie�no�ci ..................................................................................................... 22

1.2. Dlaczego warto stosowa� wspó�bie�no��? ................................................................................. 251.2.1. Stosowanie wspó�bie�no�ci do podzia�u zagadnie� ......................................................... 251.2.2. Stosowanie wspó�bie�no�ci do podniesienia wydajno�ci ................................................. 261.2.3. Kiedy nie nale�y stosowa� wspó�bie�no�ci ....................................................................... 27

1.3. Wspó�bie�no�� i wielow�tkowo�� w j�zyku C++ .................................................................... 281.3.1. Historia przetwarzania wielow�tkowego w j�zyku C++ ................................................ 291.3.2. Obs�uga wspó�bie�no�ci w nowym standardzie ............................................................... 301.3.3. Efektywno�� biblioteki w�tków j�zyka C++ .................................................................. 301.3.4. Mechanizmy zwi�zane z poszczególnymi platformami ................................................... 32

1.4. Do dzie�a! ...................................................................................................................................... 321.4.1. „Witaj �wiecie wspó�bie�no�ci” ......................................................................................... 32

1.5. Podsumowanie .............................................................................................................................. 34

Rozdzia� 2. Zarz�dzanie w�tkami 352.1. Podstawowe zarz�dzanie w�tkami ............................................................................................. 36

2.1.1 Uruchamianie w�tku .......................................................................................................... 362.1.2. Oczekiwanie na zako�czenie w�tku .................................................................................. 392.1.3. Oczekiwanie w razie wyst�pienia wyj�tku ....................................................................... 392.1.4. Uruchamianie w�tków w tle .............................................................................................. 42

2.2. Przekazywanie argumentów do funkcji w�tku ......................................................................... 432.3. Przenoszenie w�asno�ci w�tku .................................................................................................... 462.4. Wybór liczby w�tków w czasie wykonywania ........................................................................... 492.5. Identyfikowanie w�tków ............................................................................................................. 522.6. Podsumowanie .............................................................................................................................. 54

Rozdzia� 3. Wspó�dzielenie danych przez w�tki 553.1. Problemy zwi�zane ze wspó�dzieleniem danych przez w�tki ................................................. 56

3.1.1. Sytuacja wy�cigu ................................................................................................................ 583.1.2. Unikanie problematycznych sytuacji wy�cigu .................................................................. 59

3.2. Ochrona wspó�dzielonych danych za pomoc� muteksów ........................................................ 603.2.1. Stosowanie muteksów w j�zyku C++ ............................................................................. 603.2.2. Projektowanie struktury kodu z my�l� o ochronie wspó�dzielonych danych ................. 62

6 Spis tre�ci

3.2.3. Wykrywanie sytuacji wy�cigu zwi�zanych z interfejsami ................................................ 633.2.4. Zakleszczenie: problem i rozwi�zanie .............................................................................. 713.2.5. Dodatkowe wskazówki dotycz�ce unikania zakleszcze� ................................................. 733.2.6. Elastyczne blokowanie muteksów za pomoc� szablonu std::unique_lock ...................... 793.2.7. Przenoszenie w�asno�ci muteksu pomi�dzy zasi�gami .................................................... 803.2.8. Dobór w�a�ciwej szczegó�owo�ci blokad .......................................................................... 82

3.3. Alternatywne mechanizmy ochrony wspó�dzielonych danych ................................................ 843.3.1. Ochrona wspó�dzielonych danych podczas inicjalizacji .................................................. 843.3.2. Ochrona rzadko aktualizowanych struktur danych .......................................................... 883.3.3. Blokowanie rekurencyjne .................................................................................................. 90

3.4. Podsumowanie .............................................................................................................................. 91

Rozdzia� 4. Synchronizacja wspó�bie�nych operacji 934.1. Oczekiwanie na zdarzenie lub inny warunek ........................................................................... 94

4.1.1. Oczekiwanie na spe�nienie warunku za pomoc� zmiennych warunkowych .................. 954.1.2. Budowa kolejki gwarantuj�cej bezpieczne przetwarzanie wielow�tkowe

przy u�yciu zmiennych warunkowych .............................................................................. 974.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci ........................................... 102

4.2.1. Zwracanie warto�ci przez zadania wykonywane w tle ................................................... 1034.2.2. Wi�zanie zadania z przysz�o�ci� ...................................................................................... 1064.2.3. Obietnice (szablon std::promise) ..................................................................................... 1094.2.4. Zapisywanie wyj�tku na potrzeby przysz�o�ci ................................................................ 1114.2.5. Oczekiwanie na wiele w�tków ........................................................................................ 112

4.3. Oczekiwanie z limitem czasowym ............................................................................................ 1154.3.1. Zegary ............................................................................................................................... 1154.3.2. Okresy ............................................................................................................................... 1174.3.3. Punkty w czasie ................................................................................................................ 1184.3.4. Funkcje otrzymuj�ce limity czasowe .............................................................................. 120

4.4. Upraszczanie kodu za pomoc� technik synchronizowania operacji ..................................... 1214.4.1. Programowanie funkcyjne przy u�yciu przysz�o�ci ....................................................... 1224.4.2. Synchronizacja operacji za pomoc� przesy�ania komunikatów ..................................... 127

4.5. Podsumowanie ............................................................................................................................ 131

Rozdzia� 5. Model pami�ci j�zyka C++ i operacje na typach atomowych 1335.1. Podstawowe elementy modelu pami�ci ................................................................................... 134

5.1.1. Obiekty i miejsca w pami�ci ............................................................................................ 1345.1.2. Obiekty, miejsca w pami�ci i przetwarzanie wspó�bie�ne ............................................ 1355.1.3. Kolejno�� modyfikacji ...................................................................................................... 136

5.2. Operacje i typy atomowe j�zyka C++ .................................................................................... 1375.2.1. Standardowe typy atomowe ............................................................................................ 1385.2.2. Operacje na typie std::atomic_flag .................................................................................. 1415.2.3. Operacje na typie std::atomic<bool> ............................................................................ 1435.2.4. Operacje na typie std::atomic<T*> — arytmetyka wskaników ................................. 1465.2.5. Operacje na standardowych atomowych typach ca�kowitoliczbowych ........................ 1475.2.6. G�ówny szablon klasy std::atomic<> ............................................................................. 1475.2.7. Wolne funkcje dla operacji atomowych .......................................................................... 150

5.3. Synchronizacja operacji i wymuszanie ich porz�dku ............................................................ 1515.3.1. Relacja synchronizacji ...................................................................................................... 1525.3.2. Relacja poprzedzania ....................................................................................................... 1545.3.3. Porz�dkowanie pami�ci na potrzeby operacji atomowych ............................................ 1555.3.4. Sekwencje zwalniania i relacja synchronizacji ............................................................... 175

Spis tre�ci 7

5.3.5. Ogrodzenia ....................................................................................................................... 1785.3.6. Porz�dkowanie operacji nieatomowych za pomoc� operacji atomowych ..................... 180

5.4. Podsumowanie ............................................................................................................................ 182

Rozdzia� 6. Projektowanie wspó�bie�nych struktur danych przy u�yciu blokad 1836.1. Co oznacza projektowanie struktur danych pod k�tem wspó�bie�no�ci? ............................ 184

6.1.1. Wskazówki dotycz�ce projektowania wspó�bie�nych struktur danych ........................ 1856.2. Projektowanie wspó�bie�nych struktur danych przy u�yciu blokad .................................... 186

6.2.1. Stos gwarantuj�cy bezpiecze�stwo przetwarzania wielow�tkowegoprzy u�yciu blokad ........................................................................................................... 187

6.2.2. Kolejka gwarantuj�ca bezpiecze�stwo przetwarzania wielow�tkowegoprzy u�yciu blokad i zmiennych warunkowych ............................................................. 190

6.2.3. Kolejka gwarantuj�ca bezpiecze�stwo przetwarzania wielow�tkowegoprzy u�yciu szczegó�owych blokad i zmiennych warunkowych .................................... 194

6.3. Projektowanie z�o�onych struktur danych przy u�yciu blokad ............................................. 2076.3.1. Implementacja tablicy wyszukiwania gwarantuj�cej bezpiecze�stwo

przetwarzania wielow�tkowego przy u�yciu blokad ...................................................... 2076.3.2. Implementacja listy gwarantuj�cej bezpiecze�stwo przetwarzania

wielow�tkowego przy u�yciu blokad .............................................................................. 2136.4. Podsumowanie ............................................................................................................................ 218

Rozdzia� 7. Projektowanie wspó�bie�nych struktur danych bez blokad 2197.1. Definicje i ich praktyczne znaczenie ....................................................................................... 220

7.1.1. Rodzaje nieblokuj�cych struktur danych ........................................................................ 2207.1.2. Struktury danych bez blokad ........................................................................................... 2217.1.3. Struktury danych bez oczekiwania ................................................................................. 2227.1.4. Zalety i wady struktur danych bez blokad ...................................................................... 222

7.2. Przyk�ady struktur danych bez blokad .................................................................................... 2237.2.1. Implementacja stosu gwarantuj�cego bezpiecze�stwo przetwarzania

wielow�tkowego bez blokad ............................................................................................ 2247.2.2. Eliminowanie niebezpiecznych wycieków — zarz�dzanie pami�ci�

w strukturach danych bez blokad .................................................................................... 2287.2.3. Wykrywanie w�z�ów, których nie mo�na odzyska�, za pomoc� wskaników ryzyka ........ 2337.2.4. Wykrywanie u�ywanych w�z�ów metod� zliczania referencji .......................................... 2427.2.5. Zmiana modelu pami�ci u�ywanego przez operacje na stosie bez blokad ................... 2477.2.6. Implementacja kolejki gwarantuj�cej bezpiecze�stwo przetwarzania

wielow�tkowego bez blokad ............................................................................................ 2527.3. Wskazówki dotycz�ce pisania struktur danych bez blokad ................................................... 264

7.3.1. Wskazówka: na etapie tworzenia prototypu nale�y stosowa� trybstd::memory_order_seq_cst ............................................................................................ 265

7.3.2. Wskazówka: nale�y u�ywa� schematu odzyskiwania pami�ci bez blokad .................... 2657.3.3 Wskazówka: nale�y unika� problemu ABA .................................................................... 2667.3.4. Wskazówka: nale�y identyfikowa� p�tle aktywnego oczekiwania i wykorzystywa�

czas bezczynno�ci na wspieranie innego w�tku ............................................................. 2677.4. Podsumowanie ............................................................................................................................ 267

Rozdzia� 8. Projektowanie wspó�bie�nego kodu 2698.1. Techniki dzielenia pracy pomi�dzy w�tki ............................................................................... 270

8.1.1. Dzielenie danych pomi�dzy w�tki przed rozpocz�ciem przetwarzania ....................... 2718.1.2. Rekurencyjne dzielenie danych ...................................................................................... 2728.1.3. Dzielenie pracy wed�ug typu zadania ............................................................................. 276

8 Spis tre�ci

8.2. Czynniki wp�ywaj�ce na wydajno�� wspó�bie�nego kodu ..................................................... 2798.2.1. Liczba procesorów ........................................................................................................... 2808.2.2. Wspó�zawodnictwo o dane i ping-pong bufora .............................................................. 2818.2.3. Fa�szywe wspó�dzielenie ................................................................................................. 2848.2.4. Jak blisko nale�y rozmie�ci� dane? ................................................................................. 2858.2.5. Nadsubskrypcja i zbyt intensywne prze��czanie zada� ................................................. 285

8.3. Projektowanie struktur danych pod k�tem wydajno�ci przetwarzania wielow�tkowego ..... 2868.3.1. Podzia� elementów tablicy na potrzeby z�o�onych operacji .......................................... 2878.3.2. Wzorce dost�pu do danych w pozosta�ych strukturach ................................................. 289

8.4. Dodatkowe aspekty projektowania wspó�bie�nych struktur danych ................................... 2918.4.1. Bezpiecze�stwo wyj�tków w algorytmach równoleg�ych .............................................. 2918.4.2. Skalowalno�� i prawo Amdahla ....................................................................................... 2988.4.3. Ukrywanie opónie� za pomoc� wielu w�tków .............................................................. 3008.4.4. Skracanie czasu reakcji za pomoc� technik przetwarzania równoleg�ego .................... 301

8.5. Projektowanie wspó�bie�nego kodu w praktyce ..................................................................... 3038.5.1. Równoleg�a implementacja funkcji std::for_each ........................................................... 3048.5.2. Równoleg�a implementacja funkcji std::find .................................................................. 3068.5.3. Równoleg�a implementacja funkcji std::partial_sum ..................................................... 312

8.6. Podsumowanie ............................................................................................................................ 322

Rozdzia� 9. Zaawansowane zarz�dzanie w�tkami 3239.1. Pule w�tków ................................................................................................................................ 324

9.1.1. Najprostsza mo�liwa pula w�tków .................................................................................. 3249.1.2. Oczekiwanie na zadania wysy�ane do puli w�tków ........................................................ 3279.1.3. Zadania oczekuj�ce na inne zadania ............................................................................... 3309.1.4. Unikanie wspó�zawodnictwa w dost�pie do kolejki zada� ............................................ 3339.1.5. Wykradanie zada� ............................................................................................................ 335

9.2. Przerywanie wykonywania w�tków .......................................................................................... 3409.2.1. Uruchamianie i przerywanie innego w�tku .................................................................... 3409.2.2. Wykrywanie przerwania w�tku ....................................................................................... 3429.2.3. Przerywanie oczekiwania na zmienn� warunkow� ........................................................ 3439.2.4. Przerywanie oczekiwania na zmienn� typu std::condition_variable_any ..................... 3469.2.5. Przerywanie pozosta�ych wywo�a� blokuj�cych ............................................................ 3489.2.6. Obs�uga przerwa� ............................................................................................................ 3499.2.7. Przerywanie zada� wykonywanych w tle podczas zamykania aplikacji ........................ 350

9.3. Podsumowanie ............................................................................................................................ 352

Rozdzia� 10. Testowanie i debugowanie aplikacji wielow�tkowych 35310.1. Rodzaje b��dów zwi�zanych z przetwarzaniem wspó�bie�nym ............................................ 354

10.1.1. Niechciane blokowanie ............................................................................................... 35410.1.2. Sytuacje wy�cigu ......................................................................................................... 355

10.2. Techniki lokalizacji b��dów zwi�zanych z przetwarzaniem wspó�bie�nym ........................ 35710.2.1. Przegl�danie kodu w celu znalezienia ewentualnych b��dów .................................. 35710.2.2. Znajdowanie b��dów zwi�zanych z przetwarzaniem wspó�bie�nym

poprzez testowanie kodu ................................................................................................. 35910.2.3. Projektowanie kodu pod k�tem �atwo�ci testowania ................................................. 36110.2.4. Techniki testowania wielow�tkowego kodu .............................................................. 36310.2.5. Projektowanie struktury wielow�tkowego kodu testowego ...................................... 36610.2.6. Testowanie wydajno�ci wielow�tkowego kodu ......................................................... 369

10.3. Podsumowanie ............................................................................................................................ 370

Spis tre�ci 9

Dodatek A Krótki przegl�d wybranych elementów j�zyka C++11 371A.1. Referencje do r-warto�ci ........................................................................................................... 371

A.1.1. Semantyka przenoszenia danych ..................................................................................... 372A.1.2. Referencje do r-warto�ci i szablony funkcji .................................................................... 375

A.2. Usuni�te funkcje ......................................................................................................................... 376A.3. Funkcje domy�lne ...................................................................................................................... 377A.4. Funkcje constexpr ...................................................................................................................... 381

A.4.1. Wyra�enia constexpr i typy definiowane przez u�ytkownika ........................................ 382A.4.2. Obiekty constexpr ............................................................................................................ 385A.4.3. Wymagania dotycz�ce funkcji constexpr ........................................................................ 385A.4.4. S�owo constexpr i szablony .............................................................................................. 386

A.5. Funkcje lambda .......................................................................................................................... 386A.5.1. Funkcje lambda odwo�uj�ce si� do zmiennych lokalnych ............................................. 388

A.6. Szablony zmiennoargumentowe ............................................................................................... 391A.6.1. Rozwijanie paczki parametrów ....................................................................................... 392

A.7. Automatyczne okre�lanie typu zmiennej ................................................................................. 395A.8. Zmienne lokalne w�tków ........................................................................................................... 396A.9. Podsumowanie ............................................................................................................................ 397

Dodatek B Krótkie zestawienie bibliotek przetwarzania wspó�bie�nego 399

Dodatek C Framework przekazywania komunikatów i kompletny przyk�adimplementacji systemu bankomatu 401

Dodatek D Biblioteka w�tków j�zyka C++ 419D.1. Nag�ówek <chrono> ................................................................................................................. 419

D.1.1. Szablon klasy std::chrono::duration ............................................................................ 420D.1.2. Szablon klasy std::chrono::time_point ....................................................................... 429D.1.3. Klasa std::chrono::system_clock ................................................................................. 431D.1.4. Klasa std::chrono::steady_clock .................................................................................. 433D.1.5. Definicja typu std::chrono::high_resolution_clock ................................................... 435

D.2. Nag�ówek <condition_variable> ............................................................................................ 435D.2.1. Klasa std::condition_variable ...................................................................................... 436D.2.2. Klasa std::condition_variable_any .............................................................................. 444

D.3. Nag�ówek <atomic> ................................................................................................................. 452D.3.1. Definicje typów std::atomic_xxx ................................................................................ 454D.3.2. Makra ATOMIC_xxx_LOCK_FREE ........................................................................ 454D.3.3. Makro ATOMIC_VAR_INIT ..................................................................................... 455D.3.4. Typ wyliczeniowy std::memory_order ....................................................................... 455D.3.5. Funkcja std::atomic_thread_fence ............................................................................. 456D.3.6. Funkcja std::atomic_signal_fence .............................................................................. 457D.3.7. Klasa std::atomic_flag .................................................................................................. 457D.3.8. Szablon klasy std::atomic ............................................................................................ 460D.3.9. Specjalizacje szablonu std::atomic ............................................................................. 471D.3.10. Specjalizacje szablonu std::atomic<typ-ca�kowitoliczbowy> ................................. 472

D.4. Nag�ówek <future> .................................................................................................................. 489D.4.1. Szablon klasy std::future ............................................................................................. 490D.4.2. Szablon klasy std::shared_future ................................................................................ 495D.4.3. Szablon klasy std::packaged_task ............................................................................... 501D.4.4. Szablon klasy std::promise .......................................................................................... 507D.4.5. Szablon funkcji std::async ........................................................................................... 513

10 Spis tre�ci

D.5. Nag�ówek <mutex> .................................................................................................................. 514D.5.1. Klasa std::mutex .......................................................................................................... 515D.5.2. Klasa std::recursive_mutex ......................................................................................... 518D.5.3. Klasa std::timed_mutex ............................................................................................... 520D.5.4. Klasa std::recursive_timed_mutex ............................................................................. 524D.5.5. Szablon klasy std::lock_guard ..................................................................................... 529D.5.6. Szablon klasy std::unique_lock ................................................................................... 530D.5.7. Szablon funkcji std::lock ............................................................................................. 540D.5.8. Szablon funkcji std::try_lock ....................................................................................... 541D.5.9. Klasa std::once_flag ..................................................................................................... 541D.5.10. Szablon funkcji std::call_once .................................................................................... 542

D.6. Nag�ówek <ratio> ..................................................................................................................... 543D.6.1. Szablon klasy std::ratio ................................................................................................ 544D.6.2. Alias szablonu std::ratio_add ...................................................................................... 544D.6.3. Alias szablonu std::ratio_subtract ............................................................................... 545D.6.4. Alias szablonu std::ratio_multiply .............................................................................. 545D.6.5. Alias szablonu std::ratio_divide .................................................................................. 546D.6.6. Szablon klasy std::ratio_equal ..................................................................................... 547D.6.7. Szablon klasy std::ratio_not_equal ............................................................................. 547D.6.8. Szablon klasy std::ratio_less ........................................................................................ 547D.6.9. Szablon klasy std::ratio_greater .................................................................................. 548D.6.10. Szablon klasy std::ratio_less_equal ............................................................................ 548D.6.11. Szablon klasy std::ratio_greater_equal ....................................................................... 548

D.7. Nag�ówek <thread> ................................................................................................................. 549D.7.1. Klasa std::thread .......................................................................................................... 549D.7.2. Przestrze� nazw std::this_thread ................................................................................ 558

Materia�y dodatkowe 561

Skorowidz 563

Synchronizacjawspó�bie�nych operacji

W tym rozdziale zostan� omówionenast�puj�ce zagadnienia:� oczekiwanie na zdarzenie;

� oczekiwanie na jednorazowe zdarzenia za pomoc�przysz�o�ci;

� oczekiwanie z limitem czasowym;

� upraszczanie kodu za pomoc� techniksynchronizowania operacji.

W poprzednim rozdziale przeanalizowali�my rozmaite sposoby ochrony danych wspó�-dzielonych przez wiele w�tków. Okazuje si� jednak, �e w pewnych przypadkach jestpotrzebna nie tyle ochrona danych, co synchronizacja dzia�a� podejmowanych przezró�ne w�tki. W�tek mo�e na przyk�ad czeka� z realizacj� w�asnej operacji na zako�-czenie pewnego zadania przez inny w�tek. Ogólnie w wielu przypadkach w�tek oczeku-j�cy na okre�lone zdarzenie lub spe�nienie pewnego warunku jest najwygodniejszymrozwi�zaniem. Mimo �e analogiczne rozwi�zanie mo�na zaimplementowa� w formiemechanizmu okresowego sprawdzania flagi zako�czonego zadania lub innej warto�cizapisanej we wspó�dzielonych danych, taki model by�by daleki od idea�u. Konieczno��synchronizacji operacji wykonywanych przez ró�ne w�tki jest do�� typowym scena-riuszem, zatem biblioteka standardowa j�zyka C++ oferuje mechanizmy u�atwiaj�ceobs�ug� tego modelu, w tym zmienne warunkowe i tzw. przysz�o�ci.

W tym rozdziale omówi� techniki oczekiwania na zdarzenia przy u�yciu zmiennychwarunkowych oraz sposoby upraszczania synchronizacji operacji za pomoc� przysz�o�ci.

94 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

4.1. Oczekiwanie na zdarzenie lub inny warunek

Przypu��my, �e podró�ujemy nocnym poci�giem. Jednym ze sposobów zagwaranto-wania, �e wysi�dziemy na w�a�ciwej stacji, jest unikanie snu i sprawdzanie wszystkichstacji, na których zatrzymuje si� nasz poci�g. W ten sposób nie przegapimy naszej stacji,jednak po dotarciu na miejsce b�dziemy bardzo zm�czeni. Alternatywnym rozwi�za-niem jest sprawdzenie rozk�adu jazdy pod k�tem godziny przyjazdu, ustawienie budzikaz pewnym wyprzedzeniem wzgl�dem tej godziny i pój�cie spa�. To rozwi�zanie jestdo�� bezpieczne — nie przegapimy naszej stacji, ale je�li poci�g si� spóni, wstaniemyzbyt wcze�nie. Nie mo�na te� wykluczy� sytuacji, w której wyczerpi� si� bateriew budziku — w takim przypadku mo�emy zaspa� i przegapi� swoj� stacj�. Idealnymrozwi�zaniem by�aby mo�liwo�� pój�cia spa� i skorzystania z pomocy czego� (lub kogo�),co obudzi�oby nas bezpo�rednio przed osi�gni�ciem stacji docelowej.

Jaki to ma zwi�zek z w�tkami? Je�li jeden w�tek czeka, a� inny w�tek zako�czy jakie�zadanie, ma do wyboru kilka mo�liwych rozwi�za�. Po pierwsze, mo�e stale spraw-dza� odpowiedni� flag� we wspó�dzielonych danych (chronionych przez muteks); flagazostanie ustawiona przez drugi w�tek w momencie zako�czenia zadania. Takie rozwi�-zanie jest nieefektywne z dwóch powodów: w�tek, który wielokrotnie sprawdza wspo-mnian� flag�, zajmuje cenny czas procesora, a muteks zablokowany przez oczekuj�cyw�tek nie jest dost�pny dla �adnego innego w�tku. Oba te czynniki dzia�aj� na nieko-rzy�� oczekuj�cego w�tku, poniewa� ten w�tek zajmuje zasoby potrzebne tak�e dodzia�ania w�tku, na który czeka, co opónia wykonanie zadania i ustawienie odpowied-niej flagi. Sytuacja przypomina unikanie snu przez ca�� podró� poci�giem i prowadze-nie rozmowy z maszynist� — maszynista zaj�ty rozmow� musi prowadzi� poci�g niecowolniej, zatem póniej dotrzemy na swoj� stacj�. Podobnie w�tek oczekuj�cy zajmujezasoby, które mog�yby by� u�ywane przez pozosta�e w�tki w systemie, przez co czasoczekiwania mo�e by� d�u�szy, ni� to konieczne.

Druga opcja polega na przechodzeniu w�tku oczekuj�cego w stan u�pienia nakrótkie momenty i okresowym wykonywaniu testów za pomoc� funkcji std::this_�thread::sleep_for() (patrz punkt 4.3):

bool flag;std::mutex m;

void wait_for_flag(){ std::unique_lock<std::mutex> lk(m); while(!flag) { lk.unlock(); Odblokowuje muteks std::this_thread::sleep_for(std::chrono::milliseconds(100)); Czeka 100 ms lk.lock(); Ponownie blokuje muteks }}

Wywo�anie funkcji w p�tli odblokowuje muteks przed przej�ciem w stan u�pienia i ponownie blokuje ten muteks po wyj�ciu z tego stanu — dzi�ki temu drugi w�tekma szanse uzyskania dost�pu do flagi i jej ustawienia.

Opisane rozwi�zanie jest o tyle dobre, �e u�piony w�tek nie zajmuje bezproduk-tywnie czasu procesora. Warto jednak pami�ta�, �e dobór w�a�ciwego czasu u�pienia

4.1. Oczekiwanie na zdarzenie lub inny warunek 95

jest do�� trudny. Zbyt krótki czas przebywania w tym stanie spowoduje, �e w�tek b�dzietraci� czas procesora na zbyt cz�ste testy; zbyt d�ugi czas u�pienia b�dzie oznacza�, �ew�tek b�dzie przebywa� w tym stanie nawet po zako�czeniu zadania, na które oczekuje,zatem opónienie w dzia�aniu w�tku oczekuj�cego b�dzie zbyt du�e. Takie „zaspanie”w�tku rzadko ma bezpo�redni wp�yw na wynik operacji wykonywanych przez program,ale ju� w przypadku szybkiej gry mo�e powodowa� pomini�cie niektórych klatekanimacji, a w przypadku aplikacji czasu rzeczywistego mo�e oznacza� pomini�cieprzydzia�u czasu procesora.

Trzecim, najlepszym rozwi�zaniem jest u�ycie gotowych elementów biblioteki stan-dardowej j�zyka C++ umo�liwiaj�cych oczekiwanie na okre�lone zdarzenie. Najprost-szym mechanizmem oczekiwania na zdarzenie generowane przez inny w�tek (na przy-k�ad zdarzenie polegaj�ce na umieszczeniu dodatkowego zadania w potoku) jest tzw.zmienna warunkowa. Zmienna warunkowa jest powi�zana z pewnym zdarzeniem lubwarunkiem oraz co najmniej jednym w�tkiem, który czeka na spe�nienie tego warunku.W�tek, który odkrywa, �e warunek jest spe�niony, mo�e powiadomi� pozosta�e w�tkioczekuj�ce na t� zmienn� warunkow�, aby je obudzi� i umo�liwi� im dalsze przetwarzanie.

4.1.1. Oczekiwanie na spe�nienie warunkuza pomoc� zmiennych warunkowych

Biblioteka standardowa j�zyka C++ udost�pnia dwie implementacje mechanizmuzmiennych warunkowych w formie klas std::condition_variable i std::condition_�variable_any. Obie klasy zosta�y zadeklarowane w pliku nag�ówkowym <condition_�variable>. W obu przypadkach zapewnienie w�a�ciwej synchronizacji wymaga u�y-cia muteksu — pierwsza klasa jest przystosowana tylko do obs�ugi muteksów typustd::mutex, natomiast druga klasa obs�uguje wszystkie rodzaje muteksów spe�niaj�cychpewien minimalny zbiór kryteriów (st�d przyrostek _any). Poniewa� klasa std::condition_�variable_any jest bardziej uniwersalna, z jej stosowaniem wi��� si� dodatkowekoszty w wymiarze wielko�ci, wydajno�ci i zasobów systemu operacyjnego. Je�li wi�cnie potrzebujemy dodatkowej elastyczno�ci, powinni�my stosowa� klas� std::condition_�variable.

Jak nale�a�oby u�y� klasy std::condition_variable do obs�ugi przyk�adu opisanegona pocz�tku tego podrozdzia�u — jak sprawi�, �e w�tek oczekuj�cy na wykonanie jakie-go� zadania b�dzie u�piony do momentu, w którym b�d� dost�pne dane do przetwo-rzenia? Na listingu 4.1 pokazano przyk�ad kodu implementuj�cego odpowiednie roz-wi�zanie przy u�yciu zmiennej warunkowej.

Listing 4.1. Oczekiwanie na dane do przetworzenia za pomoc� klasystd::condition_variable

std::mutex mut;std::queue<data_chunk> data_queue; std::condition_variable data_cond;

void data_preparation_thread(){ while(more_data_to_prepare()) { data_chunk const data=prepare_data();

96 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

std::lock_guard<std::mutex> lk(mut); data_queue.push(data); data_cond.notify_one(); }}

void data_processing_thread(){ while(true) { std::unique_lock<std::mutex> lk(mut); data_cond.wait( lk,[]{return !data_queue.empty();}); data_chunk data=data_queue.front(); data_queue.pop(); lk.unlock(); process(data); if(is_last_chunk(data)) break; }}

Na pocz�tku kodu zdefiniowano kolejk� , która b�dzie u�ywana do przekazywaniadanych pomi�dzy dwoma w�tkami. Kiedy dane s� gotowe do przetworzenia, w�tek,który je przygotowa�, blokuje muteks chroni�cy kolejk� za pomoc� klasy std::lock_�guard i umieszcza nowe dane w kolejce . W�tek wywo�uje nast�pnie funkcj� sk�a-dow� notify_one() dla obiektu klasy std::condition_variable, aby powiadomi� ocze-kuj�cy w�tek (je�li taki istnieje) o dost�pno�ci nowych danych .

W tym modelu drug� stron� komunikacji jest w�tek przetwarzaj�cy te dane. W�tekprzetwarzaj�cy najpierw blokuje muteks, jednak tym razem u�yto do tego celu klasystd::unique_lock zamiast klasy std::lock_guard — przyczyny tej decyzji zostan�wyja�nione za chwil�. W�tek wywo�uje nast�pnie funkcj� wait() dla obiektu klasystd::condition_variable. Na wej�ciu tego wywo�ania w�tek przekazuje obiekt blokadyi funkcj� lambda reprezentuj�c� warunek, który musi zosta� spe�niony przed przyst�-pieniem do dalszego przetwarzania . Funkcje lambda to stosunkowo nowy element(wprowadzony w standardzie C++11), który umo�liwia pisanie funkcji anonimowychw ramach innych wyra�e�. Wspomniane rozwi�zanie wprost idealnie nadaje si� dowskazywania predykatów w wywo�aniach takich funkcji biblioteki standardowej jakwait(). W tym przypadku prosta funkcja lambda []{return !data_queue.empty();}sprawdza, czy struktura reprezentowana przez zmienn� data_queue nie jest pusta, tj.czy kolejka zawiera jakie� dane gotowe do przetworzenia. Funkcje lambda zostan� szcze-gó�owo omówione w cz��ci A.5 dodatku A.

Implementacja funkcji wait() sprawdza warunek (wywo�uj�c przekazan� funkcj�lambda), po czym zwraca sterowanie, je�li ten warunek jest spe�niony (je�li funkcjalambda zwróci�a warto�� true). Je�li warunek nie jest spe�niony (je�li funkcja lambdazwróci�a warto�� false), funkcja wait() odblokowuje muteks i wprowadza bie��cy w�tekw stan blokady (oczekiwania). Kiedy zmienna warunkowa jest powiadamiana za pomoc�funkcji notify_one() wywo�anej przez w�tek przygotowuj�cy dane, w�tek oczekuj�cy jestbudzony (odblokowywany), ponownie uzyskuje blokad� muteksu i jeszcze raz sprawdzawarunek. Je�li warunek dalszego przetwarzania jest spe�niony, funkcja wait() zwraca

4.1. Oczekiwanie na zdarzenie lub inny warunek 97

sterowanie z zachowaniem blokady muteksu. Je�li warunek nie jest spe�niony, w�tekodblokowuje muteks i ponownie przechodzi w stan oczekiwania. W�a�nie dlatego w przy-k�adzie nale�a�o u�y� klasy std::unique_lock zamiast klasy std::lock_guard — w�tekoczekuj�cy musi odblokowa� muteks na czas oczekiwania i zablokowa� go ponowniepo otrzymaniu powiadomienia, a klasa std::lock_guard nie zapewnia takiej elastycz-no�ci. Gdyby muteks pozosta� zablokowany przez ca�y czas u�pienia tego w�tku, w�tekprzygotowuj�cy dane nie móg�by zablokowa� tego muteksu i doda� elementu do kolejki,zatem warunek budzenia w�tku oczekuj�cego nigdy nie zosta�by spe�niony.

Na listingu 4.1 u�y�em prostej funkcji lambda , która sprawdza, czy strukturakolejki nie jest pusta. Okazuje si�, �e w tej roli równie dobrze mo�na by u�y� dowolnejfunkcji lub obiektu wywo�ywalnego. Je�li programista dysponuje ju� funkcj� spraw-dzaj�c� odpowiedni warunek (funkcja mo�e oczywi�cie by� nieporównanie bardziejz�o�ona ni� prosty test z powy�szego przyk�adu), mo�e przekaza� t� funkcj� bezpo-�rednio na wej�ciu funkcji wait(), bez konieczno�ci opakowywania jej w ramach wyra-�enia lambda. Po wywo�aniu funkcji wait() zmienna warunkowa mo�e sprawdzi�wskazany warunek na wiele ró�nych sposobów, jednak podczas tego testu mutekszawsze jest zablokowany, a funkcja wait() natychmiast zwraca sterowanie, pod warun-kiem �e przekazana funkcja sprawdzaj�ca ten warunek zwróci�a warto�� true. Je�liw�tek oczekuj�cy ponownie uzyskuje muteks i sprawdza warunek, mimo �e nie otrzy-ma� powiadomienia od innego w�tku i jego dzia�ania nie s� bezpo�redni� odpowiedzi�na takie powiadomienie, mamy do czynienia z tzw. pozornym budzeniem (ang. spu-rious wake). Poniewa� optymalna liczba i cz�stotliwo�� takich pozornych budze� s�z definicji trudne do oszacowania, funkcja sprawdzaj�ca prawdziwo�� warunku niepowinna powodowa� �adnych skutków ubocznych. Gdyby ta funkcja powodowa�a skutkiuboczne, programista musia�by przygotowa� swój kod na wielokrotne wyst�powanietych skutków przed spe�nieniem warunku.

Mo�liwo�� odblokowania obiektu klasy std::unique_lock nie jest u�ywana tylkodla wywo�ania funkcji wait() — analogiczne rozwi�zanie zastosowali�my po uzyskaniudanych do przetworzenia, ale przed przyst�pieniem do w�a�ciwego przetwarzania .Przetwarzanie danych mo�e by� czasoch�onn� operacj�, a jak wiemy z rozdzia�u 3.,utrzymywanie blokady muteksu d�u�ej, ni� to konieczne, nie jest dobrym rozwi�zaniem.

Stosowanie struktury kolejki do przekazywania danych pomi�dzy w�tkami (jak nalistingu 4.1) jest do�� typowym rozwi�zaniem. Je�li projekt aplikacji jest w�a�ciwy,synchronizacja powinna dotyczy� samej kolejki, co znacznie ogranicza liczb� poten-cjalnych problemów i problematycznych sytuacji wy�cigu. Spróbujmy wi�c wyodr�b-ni� z listingu 4.1 uniwersaln� kolejk� gwarantuj�c� bezpieczne przetwarzanie wielo-w�tkowe.

4.1.2. Budowa kolejki gwarantuj�cej bezpieczne przetwarzaniewielow�tkowe przy u�yciu zmiennych warunkowych

Przed przyst�pieniem do projektowania uniwersalnej kolejki warto po�wi�ci� kilkaminut analizie operacji, które trzeba b�dzie zaimplementowa� dla tej struktury danych(podobnie jak w przypadku stosu gwarantuj�cego bezpiecze�stwo przetwarzania wielo-w�tkowego z punktu 3.2.3). Przyjrzyjmy si� kontenerowi std::queue<> dost�pnemuw bibliotece standardowej j�zyka C++ (patrz listing 4.2), który b�dzie stanowi� punktwyj�cia dla naszej implementacji.

98 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Listing 4.2. Interfejs kontenera std::queue

template <class T, class Container = std::deque<T> >class queue {public: explicit queue(const Container&); explicit queue(Container&& = Container());

template <class Alloc> explicit queue(const Alloc&); template <class Alloc> queue(const Container&, const Alloc&); template <class Alloc> queue(Container&&, const Alloc&); template <class Alloc> queue(queue&&, const Alloc&);

void swap(queue& q);

bool empty() const; size_type size() const;

T& front(); const T& front() const; T& back(); const T& back() const;

void push(const T& x); void push(T&& x); void pop(); template <class... Args> void emplace(Args&&... args);};

Je�li pominiemy operacje konstruowania, przypisywania i wymiany, pozostan� namzaledwie trzy grupy operacji: operacje zwracaj�ce stan ca�ej kolejki (empty() i size()),operacje zwracaj�ce pojedyncze elementy kolejki (front() i back()) oraz operacjemodyfikuj�ce kolejk� (push(), pop() i emplace()). Mamy wi�c do czynienia z sytuacj�analogiczn� do tej opisanej w punkcie 3.2.3 (gdzie omawiali�my struktur� stosu), zatemopisany interfejs jest nara�ony na te same problemy zwi�zane z sytuacjami wy�cigów.W tym przypadku nale�y po��czy� funkcje front() i pop() w jedno wywo�anie, tak jakwcze�niej po��czyli�my funkcje top() i pop() dla struktury stosu. Warto jeszcze zwróci�uwag� na pewien nowy element w kodzie z listingu 4.1 — podczas u�ywania kolejkido przekazywania danych pomi�dzy w�tkami w�tek docelowy zwykle musi czeka� nate dane. Warto wi�c zaimplementowa� funkcj� pop() w dwóch wersjach — pierwszafunkcja, try_pop(), próbuje pobra� warto�� z kolejki, ale zawsze zwraca sterowaniebezpo�rednio po wywo�aniu, nawet je�li kolejka nie zawiera�a �adnej warto�ci (wtedyfunkcja sygnalizuje b��d); druga funkcja, wait_and_pop(), czeka na pojawienie si�w kolejce warto�ci do pobrania. Po wprowadzeniu zmian zgodnie ze schematem opi-sanym ju� przy okazji przyk�adu stosu interfejs struktury kolejki powinien wygl�da�tak jak na listingu 4.3.

Listing 4.3. Interfejs struktury danych threadsafe_queue

#include <memory> Dla typu std::shared_ptr

template<typename T>class threadsafe_queue

4.1. Oczekiwanie na zdarzenie lub inny warunek 99

{public: threadsafe_queue(); threadsafe_queue(const threadsafe_queue&); threadsafe_queue& operator=( const threadsafe_queue&) = delete; Dla uproszczenia wyklucza mo�liwo�� przypisywania void push(T new_value);

bool try_pop(T& value); std::shared_ptr<T> try_pop();

void wait_and_pop(T& value); std::shared_ptr<T> wait_and_pop();

bool empty() const;};

Podobnie jak w przypadku stosu, na listingu 4.3 usuni�to konstruktory i operatorprzypisania, aby upro�ci� analizowany kod. Tak jak wcze�niej, tak�e tym razem funk-cje try_pop() i wait_for_pop() wyst�puj� w dwóch wersjach. Pierwsza przeci��onawersja funkcji try_pop() zapisuje pobran� warto�� we wskazywanej zmiennej, takaby mo�na by�o u�y� tej warto�ci w roli statusu; funkcja zwraca warto�� true, je�liuzyska�a jak�� warto�� — w przeciwnym razie funkcja zwraca warto�� false (patrzcz��� A.2 dodatku A). Druga przeci��ona wersja nie mo�e dzia�a� w ten sam spo-sób, poniewa� natychmiast zwraca uzyskan� warto��. Je�li jednak funkcja nie uzyska�a�adnej warto�ci, mo�e zwróci� wskanik równy NULL.

Jaki to ma zwi�zek z listingiem 4.1? Okazuje si�, �e mo�emy wyodr�bni� kod funkcjipush() i wait_and_pop() z tamtego listingu i na tej podstawie przygotowa� now� imple-mentacj� (patrz listing 4.4).

Listing 4.4. Funkcje push() i wait_and_pop() wyodr�bnione z listingu 4.1

#include <queue>#include <mutex>#include <condition_variable>

template<typename T>class threadsafe_queue{private: std::mutex mut; std::queue<T> data_queue; std::condition_variable data_cond;public: void push(T new_value) { std::lock_guard<std::mutex> lk(mut); data_queue.push(new_value); data_cond.notify_one(); }

void wait_and_pop(T& value) {

100 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk,[this]{return !data_queue.empty();}); value=data_queue.front(); data_queue.pop(); }};

threadsafe_queue<data_chunk> data_queue;

void data_preparation_thread(){ while(more_data_to_prepare()) { data_chunk const data=prepare_data(); data_queue.push(data); }}

void data_processing_thread(){ while(true) { data_chunk data; data_queue.wait_and_pop(data); process(data); if(is_last_chunk(data)) break; }}

Muteks i zmienna warunkowa s� teraz elementami sk�adowymi obiektu klasy threadsafe_�queue, zatem nie jest potrzebne stosowanie odr�bnych zmiennych , a wywo�aniefunkcji push() nie wymaga zewn�trznych mechanizmów synchronizacji . Jak wida�,tak�e funkcja wait_and_pop() uwzgl�dnia stan zmiennej warunkowej .

Napisanie drugiej wersji przeci��onej funkcji wait_and_pop() nie stanowi �adnegoproblemu; tak�e pozosta�e funkcje mo�na niemal skopiowa� z przyk�adu stosu pokaza-nego na listingu 3.5. Ostateczn� wersj� implementacji kolejki pokazano na listingu 4.5.

Listing 4.5. Kompletna definicja klasy kolejki gwarantuj�cej bezpiecze�stwoprzetwarzania wielow�tkowego (dzi�ki u�yciu zmiennych warunkowych)

#include <queue>#include <memory>#include <mutex>#include <condition_variable>

template<typename T>class threadsafe_queue{private: mutable std::mutex mut; Muteks musi by� modyfikowalny std::queue<T> data_queue; std::condition_variable data_cond;public: threadsafe_queue() {}

4.1. Oczekiwanie na zdarzenie lub inny warunek 101

threadsafe_queue(threadsafe_queue const& other) { std::lock_guard<std::mutex> lk(other.mut); data_queue=other.data_queue; }

void push(T new_value) { std::lock_guard<std::mutex> lk(mut); data_queue.push(new_value); data_cond.notify_one(); }

void wait_and_pop(T& value) { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk,[this]{return !data_queue.empty();}); value=data_queue.front(); data_queue.pop(); }

std::shared_ptr<T> wait_and_pop() { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk,[this]{return !data_queue.empty();}); std::shared_ptr<T> res(std::make_shared<T>(data_queue.front())); data_queue.pop(); return res; }

bool try_pop(T& value) { std::lock_guard<std::mutex> lk(mut); if(data_queue.empty()) return false; value=data_queue.front(); data_queue.pop(); return true; }

std::shared_ptr<T> try_pop() { std::lock_guard<std::mutex> lk(mut); if(data_queue.empty()) return std::shared_ptr<T>(); std::shared_ptr<T> res(std::make_shared<T>(data_queue.front())); data_queue.pop(); return res; }

bool empty() const { std::lock_guard<std::mutex> lk(mut); return data_queue.empty(); }};

102 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Mimo �e empty() jest sta�� funkcj� sk�adow� i mimo �e parametr other konstruktorakopiuj�cego jest sta�� referencj�, pozosta�e w�tki mog� dysponowa� niesta�ymi refe-rencjami do tego obiektu i wywo�ywa� funkcje sk�adowe zmieniaj�ce jego stan, zatemblokowanie muteksu wci�� jest konieczne. Poniewa� blokowanie muteksu jest operacj�zmieniaj�c� stan obiektu, obiekt muteksu nale�y oznaczy� jako modyfikowalny (ang.mutable) , tak aby mo�na by�o blokowa� ten muteks w ciele funkcji empty() i kon-struktora kopiuj�cego.

Zmienne warunkowe s� przydatne tak�e w sytuacji, w której wiele w�tków czeka nato samo zdarzenie. Je�li celem stosowania w�tków jest dzielenie obci��enia i je�li tylkojeden w�tek powinien reagowa� na powiadomienie, mo�na zastosowa� dok�adnie tak�sam� struktur� jak ta z listingu 4.1 — wystarczy uruchomi� wiele instancji w�tku prze-twarzaj�cego dane. Po przygotowaniu nowych danych wywo�anie funkcji notify_one()spowoduje, �e jeden z w�tków aktualnie wykonuj�cych funkcj� wait() sprawdzi waru-nek. Poniewa� do struktury data_queue w�a�nie dodano nowe dane, funkcja wait()zwróci sterowanie. Nie wiadomo, do którego w�tku trafi powiadomienie ani nawet czyistnieje w�tek oczekuj�cy na to powiadomienie (nie mo�na przecie� wykluczy�, �ewszystkie w�tki w danej chwili przetwarzaj� swoje dane).

Warto te� pami�ta� o mo�liwo�ci oczekiwania na to samo zdarzenie przez wielew�tków, z których ka�dy musi zareagowa� na powiadomienie. Opisany scenariusz mo�emie� zwi�zek z inicjalizacj� wspó�dzielonych danych, gdzie wszystkie w�tki przetwa-rzaj�ce operuj� na tych samych danych i musz� czeka� albo na ich inicjalizacj� (w takimprzypadku istniej� lepsze mechanizmy — patrz punkt 3.3.1 w rozdziale 3.), albo na ichaktualizacj� (na przyk�ad w ramach okresowej, wielokrotnej inicjalizacji). W opisanychprzypadkach w�tek przygotowuj�cy dane mo�e wywo�a� funkcj� sk�adow� notify_all()dla zmiennej warunkowej (zamiast funkcji notify_one()). Jak nietrudno si� domy�li�,funkcja powoduje, �e wszystkie w�tki aktualnie wykonuj�ce funkcj� wait() sprawdz�warunek, na który czekaj�.

Je�li w�tek wywo�uj�cy w za�o�eniu ma oczekiwa� na dane zdarzenie tylko raz, czylije�li po spe�nieniu warunku w�tek nie b�dzie ponownie czeka� na t� sam� zmienn�warunkow�, by� mo�e warto zastosowa� inny mechanizm synchronizacji ni� zmiennawarunkowa. Zmienne warunkowe s� szczególnie nieefektywne w sytuacji, gdy warun-kiem, na który oczekuj� w�tki, jest dost�pno�� okre�lonego elementu danych. W takimprzypadku lepszym rozwi�zaniem jest u�ycie mechanizmu przysz�o�ci.

4.2. Oczekiwanie na jednorazowe zdarzeniaza pomoc� przysz�o�ci

Przypu��my, �e planujemy podró� samolotem. Po przyjedzie na lotnisko i przej�ciurozmaitych procedur wci�� musimy czeka� na komunikat dotycz�cy gotowo�ci naszegosamolotu na przyj�cie pasa�erów (zdarza si�, �e pasa�erowie musz� czeka� wiele godzin).Mo�emy oczywi�cie znale� sposób, aby ten czas min�� nieco szybciej (mo�emy naprzyk�ad czyta� ksi��k�, przegl�da� strony internetowe lub uda� si� na posi�ek dodrogiej lotniskowej kawiarni), jednak niezale�nie od sposobu sp�dzania czasu czekamyna jedno — sygna� wzywaj�cy do udania si� na pok�ad samolotu. Co wi�cej, interesu-j�cy nas lot odb�dzie si� tylko raz, zatem przy okazji nast�pnego wyjazdu na wakacjeb�dziemy czekali na inny lot.

4.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci 103

Twórcy biblioteki standardowej j�zyka C++ rozwi�zali problem jednorazowychzdarze� za pomoc� mechanizmu nazwanego przysz�o�ci� (ang. future). W�tek, którymusi czeka� na okre�lone jednorazowe zdarzenie, powinien uzyska� przysz�o�� repre-zentuj�c� to zdarzenie. W�tek oczekuj�cy na t� przysz�o�� mo�e nast�pnie okresowosprawdza�, czy odpowiednie zdarzenie nie nast�pi�o (tak jak pasa�erowie co jaki� czaszerkaj� na tablic� odlotów), i jednocze�nie pomi�dzy tymi testami wykonywa� innezadanie (spo�ywa� drogi deser w lotniskowej kawiarni). Alternatywnym rozwi�zaniemjest wykonywanie innego zadania do momentu, w którym dalsze dzia�anie nie jest mo�-liwe bez okre�lonego zdarzenia, i przej�cie w stan gotowo�ci na przysz�o��. Przysz�o��mo�e, ale nie musi by� powi�zana z danymi (tak jak tablica odlotów mo�e wskazywa�r�kawy prowadz�ce do w�a�ciwych samolotów). Po wyst�pieniu zdarzenia (po osi�gni�-ciu gotowo�ci przez przysz�o��) nie jest mo�liwe wyzerowanie tej przysz�o�ci.

W bibliotece standardowej j�zyka C++ istniej� dwa rodzaje przysz�o�ci zaimple-mentowane w formie dwóch szablonów klas zadeklarowanych w nag�ówku biblioteki<future>: przysz�o�ci unikatowe (std::future<>) oraz przysz�o�ci wspó�dzielone (std::�shared_future<>). Wymienione klasy opracowano na bazie typów std::unique_ptri std::shared_ptr. Obiekt typu std::future jest jedyn� instancj� odwo�uj�c� si� dopowi�zanego zdarzenia, natomiast do jednego zdarzenia mo�e si� odwo�ywa� wieleinstancji typu std::shared_future. W drugim przypadku wszystkie instancje s� gotowejednocze�nie i wszystkie mog� uzyskiwa� dost�p do dowolnych danych powi�zanychz danym zdarzeniem. W�a�nie z my�l� o powi�zanych danych zaprojektowano te szablonyklas — tak jak w przypadku szablonów std::unique_ptr i std::shared_ptr, parametryszablonów std::future<> i std::shared_future<> reprezentuj� w�a�nie typy powi�zanychdanych. W razie braku powi�zanych danych nale�y stosowa� nast�puj�ce specjalizacjetych szablonów: std:future<void> i std::shared_future<void>. Mimo �e przysz�o�cis�u�� do komunikacji pomi�dzy w�tkami, same obiekty przysz�o�ci nie oferuj� mecha-nizmów synchronizowanego dost�pu. Je�li wiele w�tków potrzebuje dost�pu do jednegoobiektu przysz�o�ci, nale�y chroni� ten dost�p za pomoc� muteksu lub innego mecha-nizmu synchronizacji (patrz rozdzia� 3.). Jak napisz� w punkcie 4.2.5 w dalszej cz��citego podrozdzia�u, wiele w�tków mo�e uzyskiwa� dost�p do w�asnej kopii obiektu typustd::shared_future<> bez konieczno�ci dodatkowej synchronizacji, nawet je�li wszystkiete kopie odwo�uj� si� do tego samego asynchronicznego wyniku.

Najprostszym przyk�adem jednorazowego zdarzenia jest wynik oblicze� wykonywa-nych w tle. Ju� w rozdziale 2. napisa�em, �e klasa std::thread nie udost�pnia prostychmechanizmów zwracania warto�ci wynikowych dla tego rodzaju zada�, i zapowiedzia-�em wprowadzenie odpowiednich rozwi�za� w rozdziale 4. przy okazji omawiania przy-sz�o�ci — czas zapozna� si� z tymi rozwi�zaniami.

4.2.1. Zwracanie warto�ci przez zadania wykonywane w tle

Przypu��my, �e nasza aplikacja wykonuje czasoch�onne obliczenia, które ostateczniepozwol� uzyska� oczekiwany wynik. Za�ó�my, �e warto�� wynikowa nie jest potrzebnana tym etapie dzia�ania programu. By� mo�e uda�o nam si� wymy�li� sposób poszu-kiwania odpowiedzi na pytanie o �ycie, wszech�wiat i ca�� reszt� stawiane w ksi��kach

104 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Douglasa Adamsa1. Mogliby�my oczywi�cie uruchomi� nowy w�tek, który wykonaniezb�dne obliczenia, jednak takie rozwi�zanie wi�za�oby si� z konieczno�ci� przeka-zania wyników z powrotem do w�tku g�ównego, poniewa� klasa std::thread nie oferujealternatywnego mechanizmu zwracania warto�ci wynikowych. W takim przypadkusporym u�atwieniem jest u�ycie szablonu funkcji std::async (zadeklarowanego w plikunag�ówkowym <future>).

Asynchroniczne zadanie, którego wynik nie jest potrzebny na bie��cym etapie dzia-�ania programu, mo�na rozpocz�� za pomoc� funkcji std::async. Zamiast zwracaniaobiektu klasy std::thread, który umo�liwi oczekiwanie na zako�czenie danego w�tku,funkcja std::async zwraca obiekt klasy std::future, który w przysz�o�ci b�dzie zawiera�warto�� wynikow�. W miejscu, w którym aplikacja b�dzie potrzebowa�a tej warto�ci,nale�y wywo�a� funkcj� get() dla obiektu przysz�o�ci — wywo�anie tej funkcji zablo-kuje wykonywanie bie��cego w�tku do momentu osi�gni�cia gotowo�ci przez przy-sz�o��, po czym zwróci uzyskan� warto��. Prosty przyk�ad u�ycia tych elementów poka-zano na listingu 4.6.

Listing 4.6. Przyk�ad u�ycia szablonu klasy std::future do uzyskania warto�ciwynikowej asynchronicznego zadania

#include <future>#include <iostream>

int find_the_answer_to_ltuae();void do_other_stuff();int main(){ std::future<int> the_answer=std::async(find_the_answer_to_ltuae); do_other_stuff(); std::cout<<"Odpowied� brzmi "<<the_answer.get()<<std::endl;}

Szablon funkcji std::async umo�liwia przekazywanie dodatkowych argumentów nawej�ciu wywo�ywanej funkcji — wystarczy doda� te argumenty do wywo�ania (podob-nie jak w przypadku klasy std::thread). Je�li pierwszy argument reprezentuje wskanikdo funkcji sk�adowej, drugi argument zawiera obiekt, dla którego ma zosta� wywo�anata funkcja sk�adowa (bezpo�rednio, za po�rednictwem wskanika lub poprzez opako-wanie std::ref), a pozosta�e argumenty s� przekazywane na wej�ciu tej funkcji sk�ado-wej. W przeciwnym razie drugi i kolejne argumenty s� przekazywane na wej�ciu funkcjisk�adowej lub wywo�ywalnego obiektu wskazanego za po�rednictwem pierwszego argu-mentu. Tak jak w przypadku klasy std::thread, je�li argumenty maj� posta� r-warto�ci,zostan� utworzone kopie poprzez przeniesienie oryginalnych warto�ci. Dzi�ki temumo�emy stosowa� typy oferuj�ce tylko mo�liwo�� przenoszenia zarówno w roli obiektówfunkcji, jak i w roli argumentów. Przyk�ad takiego rozwi�zania pokazano na listingu 4.7.

1 W ksi��ce Autostopem przez Galaktyk� zbudowano komputer Deep Thought, który mia� odpowiedzie�

na pytanie o �ycie, wszech�wiat i ca�� reszt�. Odpowiedzi� na to pytanie by�a liczba 42.

4.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci 105

Listing 4.7. Przekazywanie argumentów na wej�ciu funkcji w�tku std::async

#include <string>#include <future>

struct X{ void foo(int,std::string const&); std::string bar(std::string const&);};X x;auto f1=std::async(&X::foo,&x,42,"witaj");auto f2=std::async(&X::bar,x,"�egnaj");struct Y{ double operator()(double);};Y y;auto f3=std::async(Y(),3.141);auto f4=std::async(std::ref(y),2.718); Wywo�uje y(2.718)X baz(X&);std::async(baz,std::ref(x)); Wywo�uje baz(x)class move_only{public: move_only(); move_only(move_only&&) move_only(move_only const&) = delete; move_only& operator=(move_only&&); move_only& operator=(move_only const&) = delete;

void operator()();};auto f5=std::async(move_only());

Domy�lnie to od stosowanej implementacji zale�y, czy funkcja std::async uruchamianowy w�tek, czy wskazane zadanie b�dzie wykonywane w sposób synchroniczny (wów-czas bie��cy w�tek b�dzie czeka� na osi�gni�cie gotowo�ci przez przysz�o��). W wi�kszo-�ci przypadków standardowe rozwi�zanie jest wystarczaj�ce, jednak programista mo�ewybra� w�a�ciwy tryb za pomoc� dodatkowego parametru funkcji std::async przeka-zywanego przed funkcj� do wywo�ania. Wspomniany parametr typu std::launch mo�emie� albo warto�� std::launch::deferred (wówczas wywo�anie funkcji jest odk�adanedo momentu wywo�ania funkcji wait() lub get() dla danej przysz�o�ci), albo warto�� std::�launch::async (wówczas funkcja musi by� wykonywana w odr�bnym w�tku), albowarto�� std::launch::deferred | std::launch::async (wówczas decyzja nale�y doimplementacji). Ostatnia opcja jest stosowana w roli warto�ci domy�lnej. Je�li wywo-�anie funkcji jest odk�adane na przysz�o��, mo�e nigdy nie nast�pi�. Na przyk�ad:

auto f6=std::async(std::launch::async,Y(),1.2); Wykonywane w nowym w�tkuauto f7=std::async(std::launch::deferred,baz,std::ref(x));auto f8=std::async( std::launch::deferred | std::launch::async, baz,std::ref(x));auto f9=std::async(baz,std::ref(x));f7.wait(); Wywo�anie odroczonej funkcji

Wywo�uje p->foo(42,"witaj"),gdzie p jest reprezentowane przez &x

Wywo�uje tmpx.bar("�egnaj"),gdzie tmpx jest kopi� x

Wywo�uje tmpy(3.141), gdzie tmpyjest tworzone za pomoc� konstruktoraprzenosz�cego Y()

Wywo�uje tmp(), gdzie tmp jest konstruowanyna podstawie wywo�ania std::move(move_only())

Wykonywane w ramachfunkcji wait() lub get()

Wybórimplementacji

106 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Jak si� przekonasz w dalszej cz��ci tego rozdzia�u (i ponownie w rozdziale 8.), funkcjastd::async u�atwia dzielenie algorytmów na wspó�bie�nie wykonywane zadania. Oka-zuje si� jednak, �e nie jest to jedyny sposób kojarzenia obiektu typu std::future z zada-niem — alternatywnym rozwi�zaniem jest opakowanie zadania w ramach instancjiszablonu klasy std::packaged_task<> lub napisanie kodu bezpo�rednio ustawiaj�cegowarto�ci za pomoc� szablonu klasy std::promise<>. Szablon klasy std::packaged_taskjest abstrakcj� wy�szego poziomu (w porównaniu z szablonem std::promise), zatemw�a�nie ten szablon omówimy jako pierwszy.

4.2.2. Wi�zanie zadania z przysz�o�ci�

Szablon klasy std::packaged_task<> wi��e przysz�o�� z funkcj� lub wywo�ywalnymobiektem. W momencie wywo�ania obiektu typu std::packaged_task<> wywo�ana zostajepowi�zana funkcja lub wywo�ywalny obiekt, a sama przysz�o�� przechodzi w stan goto-wo�ci (warto�� wynikowa zostaje umieszczona w powi�zanych danych). Opisan� struktur�mo�na wykorzysta� w roli elementu sk�adowego podczas budowy puli w�tków (patrzrozdzia� 9.) lub dowolnego innego schematu zarz�dzania zadaniami polegaj�cego naprzyk�ad na wykonywaniu ka�dego zadania w osobnym w�tku lub sekwencyjnym wyko-nywaniu zada� w jednym w�tku dzia�aj�cym w tle. Je�li jedn� wi�ksz� operacj� mo�napodzieli� na wiele autonomicznych podzada�, ka�de z tych podzada� mo�na opakowa�w ramach obiektu klasy std::packaged_task<>, aby nast�pnie przekaza� ten obiekt domechanizmu szeregowania zada� lub do puli w�tków. W ten sposób mo�na skutecznieukry� szczegó�y zwi�zane z poszczególnymi zadaniami — mechanizm szeregowania zada�operuje na obiektach klasy std::packaged_task<>, nie na poszczególnych funkcjach.

Parametr szablonu klasy std::packaged_task<> reprezentuje sygnatur� funkcji —na przyk�ad dla funkcji, która nie otrzymuje �adnych parametrów i nie zwraca warto�ci,nale�a�oby u�y� sygnatury void(), natomiast dla funkcji otrzymuj�cej niesta�� refe-rencj� do warto�ci typu std::string i wskanik do warto�ci typu double oraz zwraca-j�cej warto�� typu int nale�a�oby u�y� sygnatury int(std::string&,double*). Podczaskonstruowania obiektu klasy std::packaged_task nale�y przekaza� funkcj� (lub wywo-�ywalny obiekt) otrzymuj�c� na wej�ciu wskazane parametry i zwracaj�c� typ, którymo�na przekonwertowa� na wskazany typ danych. Dok�adne dopasowanie typów niejest wymagane — istnieje mo�liwo�� skonstruowania obiektu klasy std::packaged_task�<double(double)> na podstawie funkcji otrzymuj�cej na wej�ciu warto�� typu inti zwracaj�cej warto�� typu float, poniewa� wymienione typy mog� by� automatyczniekonwertowane.

Typ warto�ci zwracanych przez wskazan� funkcj� identyfikuje typ zwracany przezfunkcj� sk�adow� get_future() konstruowanego obiektu klasy std::future<>, nato-miast lista argumentów zdefiniowana w ramach sygnatury funkcji jest u�ywana dowyznaczania sygnatury operatora wywo�ania funkcji zadania reprezentowanego przezten obiekt. Przyk�ad cz��ciowej definicji klasy std::packaged_task<std::string(std::�vector<char>*,int)> pokazano na listingu 4.8.

Instancja klasy std::packaged_task jest obiektem wywo�ywalnym i jako taka mo�eby� opakowana w ramach obiektu klasy std::function, przekazana do obiektu kla-sy std::thread w roli funkcji w�tku, przekazana do dowolnej innej funkcji oczekuj�cejwywo�ywalnego obiektu, a nawet bezpo�rednio wywo�ana. W momencie wywo�ania

4.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci 107

Listing 4.8. Cz��ciowa definicja specjalizacji szablonu klasystd::packaged_task<>

template<>class packaged_task<std::string(std::vector<char>*,int)>{public: template<typename Callable> explicit packaged_task(Callable&& f); std::future<std::string> get_future(); void operator()(std::vector<char>*,int);};

obiektu klasy std::packaged_task jako obiektu funkcji argumenty przekazane na wej�ciuoperatora wywo�ania s� przekazywane do opakowanej funkcji, a zwracana warto�� jestzapisywana jako asynchroniczny wynik w obiekcie typu std::future (obiekt mo�nanast�pnie uzyska� za pomoc� funkcji get_future()). Oznacza to, �e mo�emy opakowa�zadanie w obiekcie klasy std::packaged_task i uzyska� przysz�o�� przed przekazaniemtego obiektu do miejsca, gdzie zostanie wywo�any. W momencie, w którym programb�dzie potrzebowa� wyniku, wystarczy poczeka� na osi�gni�cie gotowo�ci przez t� przy-sz�o��. Praktyczny przyk�ad takiego rozwi�zania opisano w nast�pnym podpunkcie.

PRZEKAZYWANIE ZADA� POMI�DZY W�TKAMI

Wiele frameworków graficznego interfejsu u�ytkownika wymaga, aby aktualizacje tegointerfejsu by�y wykonywane przez okre�lone w�tki. Oznacza to, �e je�li jaki� inny w�tekmusi zaktualizowa� graficzny interfejs u�ytkownika, powinien wys�a� komunikat dow�a�ciwego w�tku, aby wyznaczony w�tek wykona� to zadanie w jego imieniu. Szablonklasy std::packaged_task oferuje odpowiednie rozwi�zania bez konieczno�ci stosowanianiestandardowych komunikatów dla ka�dego zadania zwi�zanego z dzia�aniem graficz-nego interfejsu u�ytkownika (patrz listing 4.9).

Listing 4.9. Uruchamianie kodu w w�tku graficznego interfejsu u�ytkownikaza pomoc� szablonu klasy std::packaged_task

#include <deque>#include <mutex>#include <future>#include <thread>#include <utility>

std::mutex m;std::deque<std::packaged_task<void()> > tasks;

bool gui_shutdown_message_received();void get_and_process_gui_message();

void gui_thread() { while(!gui_shutdown_message_received()) { get_and_process_gui_message(); std::packaged_task<void()> task; {

108 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

std::lock_guard<std::mutex> lk(m); if(tasks.empty()) continue; task=std::move(tasks.front()); tasks.pop_front(); } task(); }}

std::thread gui_bg_thread(gui_thread);

template<typename Func>std::future<void> post_task_for_gui_thread(Func f){ std::packaged_task<void()> task(f); std::future<void> res=task.get_future(); std::lock_guard<std::mutex> lk(m); tasks.push_back(std::move(task)); return res; }

Powy�szy kod jest bardzo prosty: w�tek graficznego interfejsu u�ytkownika dzia�aw p�tli do momentu otrzymania komunikatu sygnalizuj�cego konieczno�� zamkni�ciatego interfejsu . W ciele tej p�tli w�tek sprawdza komunikaty dotycz�ce graficznegointerfejsu u�ytkownika (na przyk�ad tego, �e u�ytkownik klikn�� jaki� element inter-fejsu) oraz ewentualne zadania w kolejce zada�. Je�li kolejka nie zawiera �adnych zada�

, w�tek przechodzi do nast�pnej iteracji p�tli; w przeciwnym razie w�tek odczytujezadanie z kolejki , zwalnia blokad� tej kolejki, po czym uruchamia to zadanie .W momencie zako�czenia zadania powi�zana z nim przysz�o�� przechodzi w stangotowo�ci.

Umieszczenie zadania w kolejce jest równie proste: nowe, opakowane zadanie jesttworzone na podstawie wskazanej funkcji , przysz�o�� jest uzyskiwana z obiektu zada-nia za pomoc� funkcji sk�adowej get_future() i wreszcie zadanie jest umieszczanena li�cie przed zwróceniem przysz�o�ci do kodu wywo�uj�cego . Kod, który wysy�a�komunikat do w�tku interfejsu u�ytkownika, mo�e albo poczeka� na przysz�o�� (je�liwykonanie zadania jest niezb�dne do dalszego dzia�ania), albo porzuci� t� przysz�o��(je�li nie potrzebuje wyniku przetwarzania).

W tym przyk�adzie u�yli�my do reprezentacji zada� klasy std::packaged_task�<void()>. Klasa opakowuje funkcj� (lub inny obiekt wywo�ywalny), która nie otrzy-muje �adnych parametrów i zwraca void (je�li wskazana funkcja zwraca inn� warto��,wynik zostanie porzucony). W tym przypadku zastosowano najprostsze mo�liwe zada-nie, jednak (jak ju� wiemy) szablon klasy std::packaged_task mo�e by� równie dobrzestosowany w implementacjach bardziej z�o�onych rozwi�za� — wystarczy w roli para-metru szablonu u�y� innej sygnatury funkcji, zmieni� typ zwracanych warto�ci (a wi�ctak�e typ danych przechowywanych w ramach stanu przysz�o�ci) i zmieni� typy argu-mentów operatora wywo�ania funkcji. Przedstawiony przyk�ad mo�na by �atwo rozsze-rzy� o mo�liwo�� przekazywania argumentów do zada�, które maj� by� wykonywaneprzez w�tek graficznego interfejsu u�ytkownika, i zwracania warto�ci w ramach obiektutypu std::future (zamiast samego sygna�u o zako�czeniu zadania).

4.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci 109

Co nale�y zrobi� z zadaniami, których nie mo�na wyrazi� w formie prostych wywo-�a� funkcji, i zadaniami, których wyniki mog� pochodzi� z wielu ró�nych miejsc? Obs�ugatakich przypadków wymaga jeszcze innego sposobu tworzenia przysz�o�ci — bezpo-�redniego ustawiania warto�ci za pomoc� szablonu std::promise.

4.2.3. Obietnice (szablon std::promise)

Programi�ci pracuj�cy nad aplikacjami, które musz� obs�ugiwa� wiele po��cze� sie-ciowych, cz�sto ulegaj� pokusie obs�ugi ka�dego po��czenia w osobnym w�tku, ponie-wa� takie rozwi�zanie u�atwia zrozumienie i zaimplementowanie mechanizmów komu-nikacji sieciowej. Takie rozwi�zanie sprawdza si� w przypadku niewielkiej liczbypo��cze� (a wi�c tak�e niewielkiej liczby w�tków). Okazuje si� jednak, �e w raziewzrostu liczby po��cze� opisany model staje si� nieefektywny, poniewa� du�a liczbaw�tków zajmuje zbyt wiele zasobów systemu operacyjnego, a cz�ste prze��czaniekontekstu (je�li liczba w�tków przekracza wspó�bie�no�� sprz�tow�) ma negatywnywp�yw na wydajno�� aplikacji. W skrajnych przypadkach aplikacja uruchamiaj�ca du�onowych w�tków mo�e wyczerpa� zasoby systemu operacyjnego przed osi�gni�ciem limitupo��cze� sieciowych. W�a�nie dlatego nawet w aplikacjach obs�uguj�cych bardzo du�opo��cze� sieciowych stosuje si� stosunkowo niewiele w�tków (czasem tylko jedenw�tek) odpowiedzialnych za obs�ug� tych po��cze�, zatem ka�dy w�tek musi obs�ugi-wa� wiele po��cze� jednocze�nie.

Przeanalizujmy przyk�ad w�tku obs�uguj�cego po��czenia. Pakiety danych przy-chodz� za po�rednictwem ró�nych po��cze� w przypadkowej kolejno�ci; podobniepakiety danych przeznaczone do wys�ania s� kolejkowane w przypadkowej kolejno�ci.W wielu przypadkach pozosta�e elementy aplikacji b�d� oczekiwa�y albo na wys�aniedanych, albo na otrzymanie nowego pakietu danych za po�rednictwem okre�lonego po��-czenia sieciowego.

Szablon klasy std::promise<T> umo�liwia ustawienie warto�ci (typu T), któr� w przy-sz�o�ci b�dzie mo�na odczyta� za po�rednictwem powi�zanego obiektu klasy std::�future<T>. Para klas std::promise i std::future to jeden z mechanizmów umo�li-wiaj�cych implementacj� interesuj�cego nas rozwi�zania — w�tek oczekuj�cy mo�ewstrzyma� dzia�anie w oczekiwaniu na przysz�o��, natomiast w�tek udost�pniaj�cydane mo�e u�y� obiektu obietnicy do ustawienia powi�zanej warto�ci, tak aby odpo-wiednia przysz�o�� przesz�a w stan gotowo�ci.

Obiekt klasy std::future powi�zany z danym obiektem klasy std::promise mo�nauzyska� za pomoc� funkcji sk�adowej get_future(), a wi�c tak samo jak w przypadkuobiektu klasy std::packaged_task. W momencie ustawienia warto�ci obiektu obietnicy(za pomoc� funkcji sk�adowej set_value()) obiekt przysz�o�ci przechodzi w stan goto-wo�ci i jako taki mo�e zosta� u�yty do pobrania zapisanej warto�ci. Je�li nast�pi znisz-czenie obiektu klasy std::promise bez wcze�niejszego ustawienia warto�ci, zamiastoczekiwanej warto�ci zostanie ustawiony stosowny wyj�tek. Sposób przekazywania wyj�t-ków pomi�dzy w�tkami zostanie opisany w punkcie 4.2.4.

Na listingu 4.10 pokazano przyk�ad kodu w�tku, który przetwarza po��czenia w opi-sany powy�ej sposób. W prezentowanym przyk�adzie u�yli�my pary klas std::promise�<bool> i std::future<bool> do identyfikacji udanej transmisji bloku danych wycho-dz�cych; warto�� powi�zana z obiektem przysz�o�ci ma posta� prostej flagi sukcesu

110 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

lub niepowodzenia. W przypadku pakietów przychodz�cych funkcj� danych powi�za-nych z obiektem przysz�o�ci pe�ni w�a�ciwa tre�� tych pakietów.

Listing 4.10. Obs�uga wielu po��cze� w jednym w�tku przy u�yciu obiektów obietnic

#include <future>

void process_connections(connection_set& connections){ while(!done(connections)) { for(connection_iterator connection=connections.begin(),end=connections.end(); connection!=end; ++connection) { if(connection->has_incoming_data()) { data_packet data=connection->incoming(); std::promise<payload_type>& p= connection->get_promise(data.id); p.set_value(data.payload); } if(connection->has_outgoing_data()) { outgoing_packet data= connection->top_of_outgoing_queue(); connection->send(data.payload); data.promise.set_value(true); } } }}

Funkcja process_connections() wykonuje p�tl� do momentu, w którym funkcja done()zwróci warto�� true . W ka�dej iteracji tej p�tli kod aplikacji sprawdza kolejno ka�depo��czenie i pobiera dane przychodz�ce (je�li istniej�) lub wysy�a kolejkowanedane wychodz�ce . Zak�adamy, �e pakiet przychodz�cy zawiera jaki� identyfikator i w�a-�ciwe dane. Identyfikator jest odwzorowywany na odpowiedni obiekt klasy std::promise(na przyk�ad metod� odnajdywania w kontenerze asocjacyjnym) , natomiast warto��jest przypisywana do cia�a pakietu. W przypadku pakietów wychodz�cych zastosowa-no mechanizm kolejki pakietów oczekuj�cych na wys�anie — program sprawdza stankolejki i wysy�a ewentualne pakiety dla danego po��czenia. Po wys�aniu pakietu w obiek-cie obietnicy powi�zanym z tymi danymi wychodz�cymi jest ustawiana warto�� true,która oznacza pomy�ln� transmisj� danych . Zgodno�� opisanego modelu z rzeczy-wistymi protoko�ami komunikacji sieciowej zale�y tylko od tych protoko�ów. Strukturana bazie obietnicy i przysz�o�ci nie pasuje co prawda do ka�dego scenariusza, ale podwieloma wzgl�dami przypomina model asynchronicznych operacji wej�cia-wyj�cia sto-sowany w niektórych systemach operacyjnych.

W dotychczas prezentowanym kodzie ca�kowicie ignorowali�my problem wyj�t-ków. Wyobra�enie �wiata, w którym wszystko dzia�a, jak nale�y, jest by� mo�e kusz�ce,ale nie ma wiele wspólnego z rzeczywisto�ci�. Nie mo�na wykluczy�, �e dysk zostanie

4.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci 111

zape�niony, �e program nie b�dzie móg� znale� potrzebnych danych, �e nast�pi awa-ria po��czenia sieciowego lub �e w wyniku b��du nie b�dzie dost�pna baza danych.Je�li operacja wykonywana w jednym w�tku potrzebuje do dzia�ania wyniku innegow�tku, warto uwzgl�dni� mo�liwo�� zasygnalizowania b��du w formie wyj�tku — zak�a-danie, �e w kodzie stosuj�cym obiekty klasy std::packaged_task czy std::promisewszystko zawsze b�dzie dzia�a�o prawid�owo, by�oby zbyt optymistyczne. Bibliotekastandardowa j�zyka C++ oferuje wygodne mechanizmy obs�ugi wyj�tków w tego rodzajuscenariuszach i umo�liwia zapisywanie wyj�tków w ramach wyników powi�zanych z tymiobiektami.

4.2.4. Zapisywanie wyj�tku na potrzeby przysz�o�ci

Przeanalizujmy nast�puj�cy fragment kodu. Je�li na wej�ciu funkcji square_root() prze-ka�emy warto�� -1, zg�oszony zostanie wyj�tek (to on trafi do kodu wywo�uj�cego t�funkcj�):

double square_root(double x){ if(x<0) { throw std::out_of_range(“x<0”); } return sqrt(x);}

Przypu��my teraz, �e zamiast wywo�a� funkcj� square_root() w bie��cym w�tku, jakw poni�szym wierszu:

double y=square_root(-1);

u�yjemy wywo�ania asynchronicznego w nast�puj�cej formie:

std::future<double> f=std::async(square_root,-1);double y=f.get();

Idealnym rozwi�zaniem by�oby zapewnienie dok�adnie takiego samego zachowaniajak w przypadku kodu jednow�tkowego — skoro zmiennej y w obu przypadkach jestprzypisywany wynik funkcji, w�tek wywo�uj�cy funkcj� f.get() powinien mie� dost�ptak�e do ewentualnych wyj�tków (tak jak odpowiedni kod jednow�tkowy).

Okazuje si�, �e w�a�nie tak dzia�a prezentowane rozwi�zanie: je�li funkcja square_�root wywo�ana za po�rednictwem funkcji std::async zg�osi jaki� wyj�tek, wyj�tekten zostanie zapisany w obiekcie przysz�o�ci (w miejscu dla warto�ci wynikowej), przy-sz�o�� przejdzie w stan gotowo�ci, a funkcja get() spowoduje ponowne zg�oszenie zapi-sanego wyj�tku. (Uwaga: standard j�zyka C++ nie okre�la, czy ponowne zg�oszeniedotyczy oryginalnego obiektu wyj�tku, czy jego kopii; ró�ne kompilatory i bibliotekistosuj� w tym wzgl�dzie odmienne rozwi�zania). To samo dotyczy funkcji opakowanejw ramach obiektu klasy std::packaged_task — je�li po wywo�aniu zadania opakowanafunkcja zg�osi jaki� wyj�tek, wyj�tek jest zapisywany w obiekcie przysz�o�ci zamiastw�a�ciwego wyniku. Aby ponownie zg�osi� ten wyj�tek, wystarczy wywo�a� funkcj� get().

Szablon klasy std::promise oferuje oczywi�cie analogiczne rozwi�zanie, które wymagabezpo�redniego wywo�ania funkcji. Aby zapisa� wyj�tek zamiast warto�ci wynikowej,wystarczy wywo�a� funkcj� sk�adow� set_exception() zamiast funkcji set_value().

112 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Wspomniana funkcja jest zwykle stosowana w bloku catch odpowiedzialnym za obs�ug�wyj�tku zg�oszonego w trakcie dzia�ania algorytmu — wyj�tek jest umieszczany w obiek-cie obietnicy:

extern std::promise<double> some_promise;

try{ some_promise.set_value(calculate_value());}catch(...){ some_promise.set_exception(std::current_exception());}

W powy�szym kodzie u�yto funkcji std::current_exception() do pobrania zg�oszonegowyj�tku; alternatywnym rozwi�zaniem by�oby wywo�anie funkcji std::copy_exception()w celu zapisania nowego wyj�tku bez jego bezpo�redniego zg�aszania:

some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));

Opisane rozwi�zanie jest nieporównanie bardziej czytelne ni� stosowanie bloku try-catch,je�li tylko potencjalny wyj�tek jest znany z wyprzedzeniem. W ten sposób mo�na nietylko upro�ci� kod, ale te� u�atwi� optymalizacj� tego kodu przez kompilator.

Innym sposobem zapisywania wyj�tku w przysz�o�ci jest zniszczenie obiektu klasystd::promise lub std::packaged_task powi�zanego z obiektem przysz�o�ci bez uprzed-niego wywo�ania funkcji ustawiaj�cej (w przypadku obiektu obietnicy) lub uruchomie-nia opakowanego zadania. Je�li obiekt przysz�o�ci nie b�dzie gotowy, w obu przypadkachdestruktor klasy std::promise lub std::packaged_task zapisze w powi�zanym staniewyj�tek typu std::future_error z kodem b��du std::future_errc::broken_promise.Tworz�c przysz�o��, zapowiadamy (sk�adamy obietnic�), �e udost�pnimy jak�� warto��lub jaki� wyj�tek; zniszczenie ród�a tej warto�ci lub tego wyj�tku bez uprzedniegodostarczenia zapowiedzianego zasobu �amie t� obietnic�. Gdyby w opisanym przypadkukompilator niczego nie zapisa� w obiekcie przysz�o�ci, w�tki oczekuj�ce mog�yby czeka�w niesko�czono��.

Do tej pory we wszystkich przyk�adach stosowa�em szablon klasy std::future.Warto jednak pami�ta� o pewnych ograniczeniach szablonu std::future, w tym o mo�-liwo�ci oczekiwania na wynik przez zaledwie jeden w�tek. W razie konieczno�ci zaim-plementowania modelu, w którym na jedno zdarzenie b�dzie oczekiwa�o wiele w�tków,nale�y u�y� raczej szablonu klasy std::shared_future.

4.2.5. Oczekiwanie na wiele w�tków

Mimo �e szablon klasy std::future obs�uguje wszystkie mechanizmy synchronizacjipotrzebne do przesy�ania danych pomi�dzy w�tkami, wywo�ania funkcji sk�adowychokre�lonego obiektu klasy std::future nie s� synchronizowane z wywo�aniami funkcjipozosta�ych obiektów tej klasy. Je�li wiele w�tków uzyskuje dost�p do jednego obiektuklasy std::future bez stosowania dodatkowych mechanizmów synchronizacji, aplika-cja jest nara�ona na wy�cig danych i niezdefiniowane zachowania. Problem wynikaz projektu tego rozwi�zania — szablon klasy std::future modeluje unikatow� w�asno��asynchronicznego wyniku, a jednorazowy charakter funkcji get() i tak wyklucza sens

4.2. Oczekiwanie na jednorazowe zdarzenia za pomoc� przysz�o�ci 113

wspó�bie�nego dost�pu. Skoro po pierwszym wywo�aniu funkcji get() nie mo�na ju�pobra� �adnych danych, z natury rzeczy dane mog� by� pobrane tylko przez jeden w�tek.

Je�li jednak projekt naszej aplikacji wspó�bie�nej wymaga, aby wiele w�tków mog�oczeka� na to samo zdarzenie, nie wszystko stracone — wystarczy u�y� szablonu klasystd::shared_future. O ile szablon klasy std::future oferuje tylko mo�liwo�� przeno-szenia, zatem w�asno�� przysz�o�ci mo�na przenosi� pomi�dzy ró�nymi obiektami, aletylko jeden obiekt mo�e si� jednocze�nie odwo�ywa� do jednego asynchronicznegowyniku, o tyle szablon klasy std::shared_future oferuje mo�liwo�� kopiowania, zatemmo�e istnie� wiele obiektów odwo�uj�cych si� do tego samego stanu.

W przypadku szablonu std::shared_future funkcje sk�adowe wywo�ywane dla poje-dynczego obiektu wci�� nie s� synchronizowane, zatem warunkiem unikania wy�cigówdanych w zwi�zku z dost�pem do tego samego obiektu z poziomu wielu w�tków jestochrona tego dost�pu za pomoc� blokady. Najlepszym sposobem jest kopiowanie tegoobiektu, tak aby ka�dy w�tek uzyskiwa� dost�p do w�asnej kopii. Dost�p do wspó�dzie-lonego, asynchronicznego stanu z poziomu wielu w�tków jest bezpieczny, je�li tylkoka�dy z tych w�tków uzyskuje dost�p do stanu za po�rednictwem w�asnego obiektuklasy std::shared_future. Przyk�ad takiego rozwi�zania pokazano na rysunku 4.1.

Rysunek 4.1. U�yciewielu obiektów klasystd::shared_futurew celu unikni�ciawy�cigów danych

Jednym z mo�liwych zastosowa� szablonu klasy std::shared_future jest implementa-cja równoleg�ego wykonywania jakiej� operacji w modelu zbli�onym do z�o�onegoarkusza kalkulacyjnego, gdzie ka�da komórka zawiera warto��, która mo�e by� u�ywana

114 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

we wzorach w wielu pozosta�ych komórkach. Wzory potrzebne do obliczania wynikóww komórkach zale�nych mog� u�ywa� obiektu klasy std::shared_future podczas odwo-�ywania si� do pierwszej komórki. Je�li wzory we wszystkich komórkach b�d� prze-twarzane równolegle, zadania odwo�uj�ce si� do gotowych warto�ci zostan� zrealizo-wane natychmiast, natomiast zadania zale�ne od innych, jeszcze przetwarzanych komórekb�d� musia�y poczeka� na osi�gni�cie gotowo�ci przez tamte komórki. Takie rozwi�-zanie umo�liwia maksymalne wykorzystanie dost�pnej wspó�bie�no�ci sprz�towej.

Obiekty klasy std::shared_future, które wskazuj� pewien asynchroniczny stan, s�konstruowane na podstawie obiektów klasy std::future odwo�uj�cych si� do tego stanu.Poniewa� obiekty klasy std::future nie wspó�dziel� w�asno�ci tego asynchronicznegostanu z �adnymi innymi obiektami, w�asno�� nale�y przenie�� do obiektu klasy std::�shared_future za pomoc� funkcji std::move, pozostawiaj�c oryginalny obiekt klasystd::future z pustym stanem (jak w przypadku u�ycia konstruktora domy�lnego):

std::promise<int> p;std::future<int> f(p.get_future());assert(f.valid()); Przysz�o�� f jest prawid�owastd::shared_future<int> sf(std::move(f));assert(!f.valid()); Przysz�o�� f ju� nie jest prawid�owaassert(sf.valid()); Przysz�o�� sf jest teraz prawid�owa

Obiekt przysz�o�ci f jest pocz�tkowo prawid�owy , poniewa� odwo�uje si� do asyn-chronicznego stanu obietnicy p, jednak po przeniesieniu tego stanu do obiektu sf toobiekt sf jest prawid�owy , natomiast obiekt f jest ju� nieprawid�owy .

Jak w przypadku wszystkich obiektów z mo�liwo�ci� przenoszenia, przeniesieniew�asno�ci jest wykonywane automatycznie dla r-warto�ci, zatem mo�emy skonstruowa�obiekt klasy std::shared_future bezpo�rednio na podstawie warto�ci zwróconej przezfunkcj� sk�adow� get_future() obiektu klasy std::promise:

std::promise<std::string> p;std::shared_future<std::string> sf(p.get_future()); Automatyczne przeniesienie w�asno�ci

W powy�szym kodzie w�asno�� jest przenoszona automatycznie — obiekt klasy std::�shared_future<> jest konstruowany na podstawie r-warto�ci typu std::future<std::�string> .

Szablon klasy std::future oferuje jeszcze inne rozwi�zanie u�atwiaj�ce stosowanieobiektów klasy std::shared_future przy u�yciu nowego mechanizmu automatycznegookre�lania typu zmiennej na podstawie inicjalizatora (patrz cz��� A.6 dodatku A). Sza-blon klasy std::future definiuje funkcj� sk�adow� share(), która tworzy nowy obiektklasy std::shared_future i bezpo�rednio przenosi w�asno�� do tego obiektu. U�ycie tegorozwi�zania mo�e nam oszcz�dzi� sporo pisania i znacznie u�atwia modyfikowanie kodu:

std::promise< std::map< SomeIndexType, SomeDataType, SomeComparator,SomeAllocator>::iterator> p;auto sf=p.get_future().share();

W tym przypadku typ zmiennej sf jest identyfikowany jako std::shared_future<std::map< SomeIndexType, SomeDataType, SomeComparator, SomeAllocator>::iterator>,czyli konstrukcja, której wielokrotne stosowanie w kodzie by�oby do�� k�opotliwe.

4.3. Oczekiwanie z limitem czasowym 115

W razie zmiany komparatora lub alokatora wystarczy zmodyfikowa� typ obiektu obiet-nicy; typ obiektu przysz�o�ci zostanie automatycznie zaktualizowany i dostosowany dotej zmiany.

W pewnych przypadkach dobrym rozwi�zaniem jest ograniczanie maksymalnegoczasu oczekiwania na zdarzenie (z uwagi na ograniczony czas dzia�ania okre�lonej sekcjikodu lub ze wzgl�du na istnienie innych wa�nych zada�, którymi dany w�tek mo�e si�zaj��, je�li oczekiwane zdarzenie nie wyst�pi odpowiednio wcze�nie). Z my�l� o takichprzypadkach wiele funkcji oczekiwania oferuje wersje z mo�liwo�ci� okre�lenia limituczasowego.

4.3. Oczekiwanie z limitem czasowym

Wszystkie wywo�ania blokuj�ce, które stosowali�my w dotychczasowych przyk�adach,blokowa�y wykonywanie w�tków przez nieokre�lony czas, tj. do momentu wyst�pieniaoczekiwanego zdarzenia. W wielu przypadkach takie rozwi�zanie jest wystarczaj�ce,jednak w niektórych sytuacjach lepszym wyj�ciem jest okre�lenie maksymalnego czasuoczekiwania. Stosowanie takich limitów czasowych mo�e mie� na celu potwierdzenieprawid�owego dzia�ania aplikacji (w formie komunikatu dla u�ytkownika lub innegoprocesu) lub przerwanie oczekiwania, je�li na przyk�ad u�ytkownik klikn�� przyciskAnuluj.

Istniej� dwa rodzaje limitów czasowych stosowanych dla operacji blokuj�cych: limityokre�laj�ce maksymalny czas blokowania w�tku (na przyk�ad 30 milisekund) oraz limitybezwzgl�dne, gdzie oczekiwanie nie mo�e trwa� d�u�ej ni� do okre�lonego punktuw czasie (na przyk�ad do godziny 17:30:15.045987023 dnia 30 listopada 2012 roku).Wi�kszo�� funkcji oczekuj�cych wyst�puje w wersjach obs�uguj�cych obie formy limi-tów czasowych. Wersje obs�uguj�ce wzgl�dne limity czasowe (okre�laj�ce czas trwaniaoperacji) s� oznaczane przyrostkiem _for, natomiast bezwzgl�dne limity czasowe ozna-cza si� przyrostkiem _until.

Na przyk�ad klasa std::condition_variable definiuje dwie przeci��one wersje funk-cji sk�adowej wait_for() i dwie przeci��one wersje funkcji sk�adowej wait_until(),czyli odpowiedniki obu wersji funkcji wait() uzupe�nione o obs�ug� wzgl�dnych i bez-wzgl�dnych limitów czasowych — pierwsza wersja czeka na sygna�, up�yni�cie limituczasowego lub bezpo�rednie budzenie; druga wersja w momencie budzenia sprawdzaprzekazany predykat i zwraca sterowanie, pod warunkiem �e albo ten predykat jestprawdziwy (w wyniku sygna�u umieszczonego w zmiennej warunkowej), albo osi�gni�tolimit czasowy.

Zanim przeanalizujemy szczegó�owe aspekty stosowania funkcji uwzgl�dniaj�cychlimity czasowe, warto po�wi�ci� chwil� na omówienie sposobu okre�lania czasu w j�zykuC++, w tym dost�pnych zegarów.

4.3.1. Zegary

W kontek�cie elementów biblioteki standardowej j�zyka C++ zegar jest dla programuród�em informacji o bie��cej godzinie. W szczególno�ci zegar jest klas� udost�pnia-j�c� cztery odr�bne informacje:

116 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

� bie��ca godzina;� typ warto�ci u�ywanych do reprezentowania godzin uzyskiwanych z obiektu

zegara;� okres reprezentowany przez jeden takt zegara;� to, czy takty zegara maj� sta�� d�ugo��, czyli mo�liwo�� traktowania zegara

jako stabilnego.

Bie��c� godzin� reprezentowan� przez zegar mo�na uzyska�, wywo�uj�c statyczn� funk-cj� sk�adow� now() klasy zegara; na przyk�ad funkcja std::chrono::system_clock::now()zwróci bie��c� godzin� reprezentowan� przez zegar systemowy. Typ punktów w czasiedla poszczególnych zegarów jest reprezentowany przez sk�adow� definicj� typu time_�point, zatem ka�da funkcja zegar::now() zwraca warto�� typu zegar::time_point.

Okres taktu zegara jest wyra�any w formie u�amka sekundy reprezentowanegoprzez sk�adow� definicj� typu period — w przypadku zegara wykonuj�cego 25 taktóww ci�gu sekundy period definiuje typ std::ratio<1,25>, natomiast w przypadku zegarawykonuj�cego jeden takt na 2,5 sekundy sk�adowa period definiuje typ std::ratio<5,2>.Je�li okre�lenie okresu taktu nie jest mo�liwe do momentu uruchomienia programulub je�li ten okres mo�e si� zmienia� w czasie dzia�ania aplikacji, okres mo�na zdefi-niowa� w formie �redniego czasu trwania taktu, najkrótszego mo�liwego taktu lub innejwarto�ci przewidzianej przez twórców biblioteki. Nie mo�na jednak zak�ada�, �e obser-wowany okres taktu zegara podczas jednej próby uruchomienia programu b�dzie odpo-wiada� rzeczywistemu okresowi danego zegara.

Je�li takty zegara maj� sta�� cz�stotliwo�� (niezale�nie od tego, czy ta cz�stotli-wo�� pasuje do przyj�tego okresu) i je�li nie mo�emy zmieni� d�ugo�ci taktu, mamy doczynienia z tzw. stabilnym zegarem (ang. steady clock). Sk�adowa statyczna is_steadyklasy stabilnego zegara ma warto�� true (w przypadku niestabilnego zegara ta sama sk�a-dowa ma warto�� false). Zegar systemowy (reprezentowany przez klas� std::chrono::�system_clock) zwykle nie jest stabilny, poniewa� mo�na dostosowywa� jego cz�sto-tliwo�� (nawet je�li zmiany cz�stotliwo�ci s� wprowadzane automatycznie z uwzgl�d-nieniem przesuni�� wzgl�dem zegara lokalnego). Ka�da taka zmiana mo�e spowodo-wa�, �e wywo�anie funkcji now() zwróci warto�� wcze�niejsz� ni� zwrócona przezpoprzednie wywo�anie tej funkcji, co oczywi�cie narusza wymaganie sta�ej cz�stotli-wo�ci zegara (i d�ugo�ci taktu). Jak si� za chwil� przekonasz, stabilne zegary s� szcze-gólnie przydatne podczas oblicze� z uwzgl�dnieniem limitów czasowych — z my�l�o tych i podobnych zastosowaniach twórcy biblioteki standardowej udost�pnili takizegar w formie klasy std::chrono::steady_clock. Biblioteka standardowa j�zyka C++zawiera te� inne klasy zegarów: wspomnian� wcze�niej klas� std::chrono::system_clock,która reprezentuje zegar „czasu rzeczywistego” w danym systemie i która udost�pniafunkcj� konwersji punktów w czasie na i z warto�ci typu time_t, oraz klas� std::�chrono::high_resolution_clock, która zapewnia najkrótszy mo�liwy takt zegara (a wi�ctak�e najwy�sz� mo�liw� cz�stotliwo��) spo�ród wszystkich zegarów tej biblioteki.Drugi z zegarów mo�na wykorzysta� w roli punktu wyj�cia dla definicji alternatyw-nych rozwi�za�. Wymienione zegary zdefiniowano (wraz z pozosta�ymi elementamizwi�zanymi z obs�ug� czasu) w pliku nag�ówkowym <chrono>.

Zanim przyst�pimy do omawiania metod reprezentowania punktów w czasie, wartopo�wi�ci� chwil� analizie technik reprezentowania okresów.

4.3. Oczekiwanie z limitem czasowym 117

4.3.2. Okresy

Okres (czas trwania) to najprostszy aspekt obs�ugi czasu. Okres zaimplementowanow szablonie klasy std::chrono::duration<> (wszystkie elementy j�zyka C++ zwi�zanez obs�ug� czasu i u�ywane przez bibliotek� w�tków nale�� do przestrzeni nazw std::�chrono). Pierwszy parametr tego szablonu okre�la typ reprezentacji (na przyk�adint, long lub double); drugi parametr jest u�amkiem okre�laj�cym liczb� sekund repre-zentowanych przez jednostk� okresu. Na przyk�ad liczba minut przechowywana w war-to�ci typu short jest reprezentowana przez klas� std::chrono::duration<short,std::�ratio<60,1>>, poniewa� minuta sk�ada si� z 60 sekund. Liczba milisekund prze-chowywanych w warto�ci typu double jest reprezentowana przez klas� std::chrono::�duration<double,std::ratio<1,1000>>, poniewa� ka�da milisekunda trwa jedn�tysi�czn� cz��� sekundy.

Biblioteka standardowa oferuje zbiór predefiniowanych definicji typów w prze-strzeni nazw std::chrono dla ró�nych okresów (wyra�anych w nanosekundach, mikro-sekundach, milisekundach, sekundach, minutach i godzinach). Wszystkie te definicjestosuj� na tyle elastyczne typy ca�kowitoliczbowe, �e mog� reprezentowa� na przyk�adokresy ponad 500-letnie w wybranych jednostkach czasu. Przestrze� nazw zawieratak�e definicje typów dla rz�dów wielko�ci uk�adu SI: od std::atto (10–18) do std::exa(1018) (i wi�cej, je�li tylko dana platforma obs�uguje 128-bitowe typy ca�kowitoliczbowe).Za pomoc� tych typów mo�na operowa� na niestandardowych okresach, na przyk�adklasa std::duration<double,std::centi> obs�uguje liczb� setnych cz��ci sekundy repre-zentowanych przez liczb� typu double.

Konwersja pomi�dzy okresami jest wykonywana automatycznie, pod warunkiem�e nie wymaga obci�cia warto�ci ród�owej — oznacza to, �e konwersja godzin nasekundy jest mo�liwa, ale ju� konwersja sekund na godziny nie zostanie wykonana auto-matycznie. Konwersj� mo�na te� wykona� jawnie za pomoc� funkcji std::chrono::�duration_cast<>:

std::chrono::milliseconds ms(54802);std::chrono::seconds s= std::chrono::duration_cast<std::chrono::seconds>(ms);

Poniewa� wynik jest obcinany (nie zaokr�glany), zmienna s b�dzie zawiera�a warto�� 54.Okresy obs�uguj� dzia�ania arytmetyczne, zatem mo�emy dodawa� i odejmowa�

okresy, aby otrzymywa� nowe okresy, b�d mno�y� lub dzieli� okresy przez sta�e wybra-nego typu danych (czyli pierwszego parametru szablonu klasy). Oznacza to, �e wyra�enie5*seconds(1) ma tak� sam� warto�� jak wyra�enia seconds(5) i minutes(1) – seconds(55).Liczb� jednostek sk�adaj�cych si� na dany okres mo�na uzyska� za pomoc� funkcjisk�adowej count(). Oznacza to, �e wywo�anie std::chrono::milliseconds(1234).count()zwróci warto�� 1234.

Wymuszanie oczekiwania na podstawie okresu (czasu trwania) wymaga stosowaniainstancji typu std::chrono::duration<>. Mo�emy na przyk�ad spowodowa�, �e czasoczekiwania na gotowo�� obiektu przysz�o�ci wyniesie 35 milisekund:

std::future<int> f=std::async(some_task);if(f.wait_for(std::chrono::milliseconds(35))==std::future_status::ready)do_something_with(f.get());

118 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Wszystkie funkcje oczekiwania zwracaj� status okre�laj�cy, czy koniec oczekiwaniawynika z wyczerpania limitu czasowego, czy z wyst�pienia zdarzenia, na które czeka�dany w�tek. W tym przypadku w�tek czeka na przysz�o��, zatem funkcja zwraca warto��std::future_status::timeout w przypadku wyczerpania limitu czasowego; warto��std::future_status::ready, je�li przysz�o�� jest gotowa; lub warto�� std::future_�status::deferred, je�li zadanie przysz�o�ci zosta�o od�o�one na póniej. Czas ocze-kiwania okresowego jest mierzony przy u�yciu stabilnego, wewn�trznego zegara biblio-teki, zatem 35 milisekund oznacza w�a�nie 35 milisekund, nawet je�li w czasie oczekiwa-nia zegar systemowy zostanie przestawiony (w przód lub w ty�). Nie mo�na oczywi�ciezapomina� o kaprysach systemu szeregowania zada� i o zró�nicowanej precyzji zega-rów systemów operacyjnych, które mog� spowodowa�, �e rzeczywisty czas dziel�cywywo�anie funkcji wait() od zwrócenia sterowania b�dzie du�o d�u�szy ni� 35 ms.

Skoro potrafimy ju� stosowa� okresy, mo�emy przej�� do analizy modelu punktóww czasie.

4.3.3. Punkty w czasie

Punkt w czasie jest reprezentowany przez instancj� szablonu klasy std::chrono::�time_point<>. Pierwszy parametr tego szablonu wskazuje zegar, natomiast drugi para-metr okre�la jednostki miary (w tej roli nale�y u�y� specjalizacji szablonu klasystd::chrono::duration<>). Warto�� punktu w czasie reprezentuje czas (w formie wielo-krotno�ci wskazanego okresu) od konkretnego punktu w czasie nazywanego epok� zegara.Epoka zegara jest prost� w�a�ciwo�ci�, która jednak nie jest bezpo�rednio dost�pnaani wprost definiowana przez standard j�zyka C++. Do najcz��ciej stosowanychepok nale�y pó�noc 1 stycznia 1970 roku i moment uruchomienia komputera, na którymdzia�a dana aplikacja. Zegary mog� stosowa� jedn� epok� lub ró�ne, niezale�ne epoki.Je�li dwa zegary stosuj� t� sam� epok�, definicja typu time_point w klasie jednego zegaramo�e wskazywa� drug� klas� jako typ zegara powi�zanego z dan� definicj� time_point.Mimo �e nie mo�na bezpo�rednio uzyska� epoki, mamy do dyspozycji funkcj� time_�since_epoch(), któr� mo�emy wywo�a� dla danej instancji typu time_point. Funkcjask�adowa time_since_epoch() zwraca warto�� okresu reprezentuj�c� czas od epoki zegarado okre�lonego punktu w czasie.

Punkt w czasie mo�na zdefiniowa� na przyk�ad w formie obiektu klasy std::chrono::�time_point<std::chrono::system_clock, std::chrono::minutes>. Tak skonstruowanyobiekt zawiera czas wzgl�dem zegara systemowego, tyle �e mierzony w minutach (niewed�ug standardowej precyzji zegara systemowego, czyli sekund lub cz��ci sekundy).

Na obiektach klasy std::chrono::time_point<> mo�na wykonywa� operacje doda-wania i odejmowania okresów, których wynikiem s� nowe punkty w czasie. Oznaczato, �e na przyk�ad w wyniku dodawania std::chrono::high_resolution_clock::now()+ std::chrono::nanoseconds(500) otrzymamy punkt w czasie, który nast�pi za 500nanosekund (licz�c od teraz). Wyra�enia tego typu s� przydatne podczas obliczaniabezwzgl�dnego limitu czasowego w sytuacji, gdy maksymalny czas wykonywania blokukodu jest znany z wyprzedzeniem. Takie rozwi�zanie wymaga jednak wielu wywo�a�funkcji oczekuj�cych lub funkcji poprzedzaj�cych funkcj� oczekuj�c� i zaliczanych dobloku, który podlega ograniczeniu czasowemu.

4.3. Oczekiwanie z limitem czasowym 119

Istnieje te� mo�liwo�� odj�cia jednego punktu w czasie od innego, pod warunkiem�e oba punkty bazuj� na tym samym zegarze. Wynikiem tej operacji jest okres, któryreprezentuje czas dziel�cy oba punkty. W ten sposób mo�na na przyk�ad sprawdza� czaswykonywania bloków kodu:

auto start=std::chrono::high_resolution_clock::now();do_something();auto stop=std::chrono::high_resolution_clock::now();std::cout<<”Wykonanie funkcji do_something() zaj��o “ <<std::chrono::duration<double,std::chrono::seconds>(stop-start).count() <<” sekund.”<<std::endl;

Parametr zegara szablonu klasy std::chrono::time_point<> decyduje nie tylko o epoce.W przypadku przekazania punktu w czasie na wej�ciu funkcji oczekuj�cej, która sto-suje bezwzgl�dny limit czasowy, wybrany zegar b�dzie u�ywany do mierzenia czasu.Warto pami�ta� o mo�liwo�ci zmiany wskaza� zegara i o tym, �e funkcja oczekuj�canie zwróci sterowania do momentu, w którym funkcja now() tego zegara nie zwróciwarto�ci póniejszej od wskazanego limitu czasowego. Je�li zegar zostanie przesta-wiony w przód, ��czny czas oczekiwania mo�e by� krótszy (w porównaniu z czasemmierzonym przez stabilny zegar); a je�li zegar zostanie cofni�ty, ��czny czas oczekiwa-nia zostanie wyd�u�ony.

Jak nietrudno odgadn��, punkty w czasie s� u�ywane w wersjach funkcji oczekuj�-cych z przyrostkiem _until. Typowym zastosowaniem tego rozwi�zania jest wyznacza-nie przesuni�cia wzgl�dem godziny zwracanej przez wywo�anie jaki�-zegar::now()w wybranym punkcie programu. Punkty powi�zane z zegarem systemowym mo�nauzyskiwa�, konwertuj�c instancje typu time_t za pomoc� statycznej funkcji sk�adowejstd::chrono::system_clock::to_time_point(). Je�li na przyk�ad maksymalny czas ocze-kiwania na zdarzenie powi�zane ze zmienn� warunkow� wynosi 500 milisekund, mo�nazastosowa� konstrukcj� podobn� do tej z listingu 4.11.

Listing 4.11. Oczekiwanie na zmienn� warunkow� z uwzgl�dnieniem limituczasowego

#include <condition_variable>#include <mutex>#include <chrono>

std::condition_variable cv;bool done;std::mutex m;

bool wait_loop(){ auto const timeout= std::chrono::steady_clock::now()+ std::chrono::milliseconds(500); std::unique_lock<std::mutex> lk(m); while(!done) { if(cv.wait_until(lk,timeout)==std::cv_status::timeout) break; } return done;}

120 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Rozwi�zanie z listingu 4.11 jest zalecanym sposobem oczekiwania na zmienne warun-kowe z uwzgl�dnieniem limitu czasowego w sytuacji, gdy funkcja oczekuj�ca nieotrzymuje na wej�ciu �adnego predykatu. Maksymalny czas wykonywania p�tli jestograniczony. Jak napisa�em w punkcie 4.1.1, je�li nie stosujemy dodatkowego predy-katu, operowanie na zmiennych warunkowych wymaga u�ycia p�tli, która obs�ugujenietypowe sposoby budzenia w�tku. W przypadku wywo�ania funkcji wait_for() w cielep�tli mo�e si� zdarzy�, �e warunek wznowienia dzia�ania zostanie spe�niony bezpo�red-nio przed up�ywem limitu czasowego, a w nast�pnej iteracji czas oczekiwania b�dzieliczony od pocz�tku. Taka sytuacja mo�e mie� miejsce wielokrotnie, zatem ��czny czasoczekiwania by�by nieograniczony.

Skoro dysponujemy ju� podstawowymi narz�dziami umo�liwiaj�cymi stosowanielimitów czasowych, pora omówi� funkcje, w których mo�na u�ywa� tych limitów.

4.3.4. Funkcje otrzymuj�ce limity czasowe

Najprostszym zastosowaniem limitu czasowego jest dodanie opónienia do przetwarza-nia okre�lonego w�tku, tak aby ten w�tek nie zajmowa� czasu procesora (potrzebnegoinnym w�tkom) w czasie, gdy nie wykonuje �adnych warto�ciowych zada�. Przyk�adtakiego rozwi�zania opisa�em w podrozdziale 4.1, gdzie kod wielokrotnie sprawdza�stan flagi gotowo�ci w p�tli. Do opóniania dzia�ania (usypiania) w�tków s�u�� funkcjestd::this_thread::sleep_ for() i std::this_thread::sleep_until(). Obie funkcjedzia�aj� jak proste budziki — w�tek jest usypiany albo na okre�lony okres (za pomoc�funkcji sleep_for()), albo do wskazanego punktu w czasie (za pomoc� funkcji sleep_�until()). W przyk�adowych rozwi�zaniach z podrozdzia�u 4.1 nale�a�oby u�y� funkcjisleep_for(), poniewa� w przypadku okresowo wykonywanych operacji w�tek powi-nien by� wstrzymywany na pewien czas (nie do okre�lonego punktu w czasie). Funkcjasleep_until() umo�liwia planowanie budzenia w�tku w okre�lonym punkcie w czasie.Funkcja sleep_until() mo�e wi�c s�u�y� na przyk�ad do uruchamiania procedury two-rzenia kopii zapasowej o pó�nocy, drukowania listy p�ac o 6 rano lub wstrzymywaniaw�tku do momentu wy�wietlenia nast�pnej klatki podczas odtwarzania zapisu wideo.

Usypianie w�tku to oczywi�cie nie jedyne zastosowanie limitów czasowych — jakju� wspomnia�em, limity tego typu mo�na stosowa� ��cznie ze zmiennymi warunko-wymi i przysz�o�ciami. Istnieje nawet mo�liwo�� stosowania limitów czasowych pod-czas prób uzyskania blokady muteksu, je�li tylko u�yty muteks obs�uguje takie dzia�a-nie. Standardowe muteksy typów std::mutex i std::recursive_mutex nie obs�uguj�limitów czasowych dla operacji blokowania, ale ju� muteksy typów std::timed_mutexi std::recursive_timed_mutex dopuszczaj� takie dzia�anie. Oba te typy udost�pniaj�funkcje sk�adowe try_lock_for() i try_lock_until(), które próbuj� uzyska� blokad�muteksu odpowiednio w okre�lonym czasie lub przed osi�gni�ciem okre�lonego punktuw czasie. W tabeli 4.1 opisano funkcje biblioteki standardowej j�zyka C++, które obs�u-guj� limity czasowe, wraz z ich parametrami i typami zwracanych warto�ci. W miejsceparametru opisanego jako okres nale�y przekaza� obiekt klasy std::duration<>, nato-miast parametr punkt_w_czasie nale�y zast�pi� obiektem klasy std::time_point<>.

Skoro wiemy ju�, jak dzia�aj� zmienne warunkowe, obiekty przysz�o�ci i obietnicoraz opakowane zadania, czas przeanalizowa� te mechanizmy w nieco szerszym kon-tek�cie, w szczególno�ci przyjrze� si� technikom upraszczania (za pomoc� tych mecha-nizmów) synchronizacji operacji wykonywanych przez ró�ne w�tki.

4.4. Upraszczanie kodu za pomoc� technik synchronizowania operacji 121

Tabela 4.1. Funkcje otrzymuj�ce limity czasowe

Klasa lub przestrze� nazw Funkcje Zwracane warto�ci

Przestrze� nazwstd::this_thread

sleep_for(okres)

sleep_until(punkt_w_czasie)

Nie dotyczy

std::condition_variable lubstd::condition_variable_any

wait_for(blokada, okres)

wait_until(blokada,punkt_w_czasie)

std::cv_status::timeout lubstd::cv_status::no_timeout

wait_for(blokada, okres,predykat)

wait_until(blokada,punkt_w_czasie, predykat)

bool — warto�� zwróconaprzez predykat po obudzeniuw�tku.

std::timed_mutex lubstd::recursive_timed_mutex

try_lock_for(okres)

try_lock_until�(punkt_w_czasie)

bool — warto�� true,je�li uda�o si� uzyska� blokad�;w przeciwnym razie warto��false

std::unique_lock<Typ�ZMo�liwo�ci�Blokady�Czasowej>

unique_lock�(typ_blokowalny, okres)

unique_lock(typ_blokowalny,punkt_w_czasie)

Nie dotyczy — funkcjaowns_lock() wywo�anadla nowo skonstruowanegoobiektu zwraca warto�� true,je�li uda�o si� uzyska� blokad�(w przeciwnym razie zwracawarto�� false).

try_lock_for(okres)

try_lock_until�(punkt_w_czasie)

bool — warto�� true, je�liuda�o si� uzyska� blokad�;w przeciwnym razie warto��false

std::future<TypWarto�ci>lub std::shared_future<�TypWarto�ci>

wait_for(okres)

wait_until(punkt_w_czasie)

Je�li wyczerpano limit czasufunkcji oczekuj�cej, zwracawarto��std::future_status::timeout;je�li obiekt przysz�o�cijest gotowy, zwraca warto��std::future_status::ready;je�li przysz�o�� zawieraodroczon� funkcj�, któranie zosta�a jeszcze wywo�ana,zwraca warto��std::future_status::deferred.

4.4. Upraszczanie kodu za pomoc� techniksynchronizowania operacji

Stosowanie mechanizmów synchronizacji, które opisa�em w poprzednich podrozdzia-�ach, w roli gotowych elementów sk�adowych umo�liwia programi�cie koncentrowaniesi� na samych operacjach wymagaj�cych synchronizacji, nie na mechanice tej synchro-nizacji. Mechanizmy synchronizacji pozwalaj� upro�ci� kod aplikacji cho�by dlatego,�e wprowadzaj� do �wiata programowania wspó�bie�nego du�o wi�cej elementów zna-nych z programowania funkcyjnego. Zamiast bezpo�rednio wspó�dzieli� dane pomi�dzyw�tkami, ka�de zadanie mo�e otrzymywa� potrzebne dane, a wynik przetwarzania mo�eby� przekazywany do wielu innych w�tków za po�rednictwem obiektów przysz�o�ci.

122 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

4.4.1. Programowanie funkcyjne przy u�yciu przysz�o�ci

Termin programowanie funkcyjne (ang. functional programming — FP) odnosi si� dostylu programowania, w którym wynik wywo�ania funkcji zale�y wy��cznie od para-metrów przekazanych na jej wej�ciu. Oznacza to, �e na wynik funkcji nie ma wp�ywuzewn�trzny stan. Opisane dzia�anie jest wi�c zgodne z matematycznym poj�ciem funk-cji, gdzie ka�de u�ycie jednej funkcji z tymi samymi parametrami spowoduje otrzy-manie dok�adnie takiego samego wyniku. W ten sposób dzia�a wiele matematycznychfunkcji biblioteki standardowej j�zyka C++, jak sin, cos czy sqrt, oraz prostych ope-racji na typach podstawowych, jak 3+3, 6*9 czy 1.3/4.7. Typowa funkcja nie modyfi-kuje zewn�trznego stanu; skutki wykonywania tej funkcji ograniczaj� si� tylko dozwracanej warto�ci.

Opisany model programowania u�atwia interpretacj� kodu, szczególnie je�li programzawiera elementy przetwarzania wspó�bie�nego, poniewa� wiele problemów zwi�zanychz pami�ci� wspó�dzielon� (opisanych w rozdziale 3.) w ogóle nie wyst�puje w �wiecie pro-gramowania funkcyjnego. Skoro dane wspó�dzielone nie s� modyfikowane, nie mog� wy-st�pi� sytuacje wy�cigów, zatem ochrona tych danych za pomoc� muteksów jest po prostuniepotrzebna. W�a�nie prostota tego modelu powoduje, �e takie j�zyki programowania jakHaskell2, gdzie wszystkie funkcje domy�lnie spe�niaj� warunek programowania funkcyj-nego, zyskuj� coraz wi�ksz� popularno�� w�ród programistów systemów wspó�bie�nych.Poniewa� niemal ca�y kod jest zgodny z zasadami programowania funkcyjnego, nielicznefunkcje, które modyfikuj� wspó�dzielony stan, na tyle wyró�niaj� si� spo�ród pozosta�ychelementów, �e mo�na bez trudu oceni� ich udzia� w ca�ej strukturze aplikacji.

Zalety programowania funkcyjnego nie ograniczaj� si� tylko do j�zyków, w którychten model jest domy�lnym paradygmatem. J�zyk C++ ��czy w sobie wiele paradyg-matów, zatem tak�e pisanie programów wed�ug zasad programowania funkcyjnegojest mo�liwe w tym j�zyku. W wersji C++11 programowanie funkcyjne jest jeszczeprostsze ni� w standardzie C++98 dzi�ki wprowadzeniu funkcji lambda (patrz cz��� A.5dodatku A), integracji funkcji std::bind zaczerpni�tej z biblioteki Boost i dokumentuTR1 oraz dodaniu automatycznego wnioskowania typów zmiennych (patrz cz��� A.7dodatku A). Ostatnim elementem, który u�atwia programowanie funkcyjne w j�zykuC++, s� obiekty przysz�o�ci — obiekt przysz�o�ci mo�na przekazywa� pomi�dzyw�tkami, tak aby wynik jednej operacji móg� zale�e� od wyniku innej operacji i aby tazale�no�� nie wymaga�a bezpo�redniego dost�pu do wspó�dzielonych danych.

SZYBKIE SORTOWANIE W MODELU PROGRAMOWANIA FUNKCYJNEGO

Aby lepiej zrozumie� mo�liwe zastosowania przysz�o�ci w modelu programowania funk-cyjnego, przeanalizujmy prost� implementacj� algorytmu sortowania szybkiego (ang.quicksort). Podstawowa koncepcja tego algorytmu jest do�� prosta — nale�y z listywarto�ci wybra� element dziel�cy, osiowy (ang. pivot), po czym podzieli� list� na dwazbiory, z których jeden zawiera elementy mniejsze od elementu dziel�cego, a drugizawiera elementy wi�ksze od wybranego elementu. Posortowana kopia listy jest uzyski-wana poprzez sortowanie obu podzbiorów i po��czenie odpowiednio posortowanej listyz�o�onej z warto�ci mniejszych od elementu dziel�cego, samego elementu dziel�cego

2 Patrz http://www.haskell.org/.

4.4. Upraszczanie kodu za pomoc� technik synchronizowania operacji 123

i posortowanej listy wi�kszej od elementu dziel�cego. Przyk�ad sortowania listy dziesi�ciuliczb ca�kowitych wed�ug opisanego schematu pokazano na rysunku 4.2. Na listingu 4.12pokazano sekwencyjn� implementacj� tego algorytmu opracowan� zgodnie z zasadamiprogramowania funkcyjnego; funkcja sequential_quick_sort() otrzymuje list� i zwracajej posortowan� kopi� przez warto�� (zamiast sortowa� list� przekazan� przez referen-cj�, jak w przypadku funkcji std::sort()).

Rysunek 4.2. Rekurencyjne sortowanie w modelu programowania funkcyjnego

Listing 4.12. Sekwencyjna implementacja algorytmu sortowania szybkiego

template<typename T>std::list<T> sequential_quick_sort(std::list<T> input){ if(input.empty()) { return input; } std::list<T> result; result.splice(result.begin(),input,input.begin()); T const& pivot=*result.begin();

auto divide_point=std::partition(input.begin(),input.end(), [&](T const& t){return t<pivot;});

std::list<T> lower_part; lower_part.splice(lower_part.end(),input,input.begin(), divide_point);

auto new_lower( sequential_quick_sort(std::move(lower_part))); auto new_higher( sequential_quick_sort(std::move(input)));

result.splice(result.end(),new_higher); result.splice(result.begin(),new_lower); return result;}

124 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Mimo �e zewn�trzny interfejs tej implementacji jest zgodny z regu�ami programowaniafunkcyjnego, wewn�trzne mechanizmy zaimplementowano w „tradycyjny” sposób,poniewa� konsekwentne stosowanie modelu funkcyjnego wymaga�oby wielu dodatko-wych operacji kopiowania. W roli elementu dziel�cego jest wybierany pierwszy element,który jest wyodr�bniany z listy za pomoc� funkcji splice() . Chocia� sortowanie nabazie tak wybranego elementu dziel�cego mo�e nie by� optymalne (liczba operacjiporównania i wymiany mo�e by� wi�ksza, ni� to konieczne), pozosta�e operacje nastrukturze typu std::list b�d� wykonywane szybciej dzi�ki efektywnemu przeszu-kiwaniu listy. Wiadomo, �e wyodr�bniony element dziel�cy musi si� znale� na li�ciewynikowej, zatem jest od razu umieszczany w odpowiedniej strukturze. Poniewa�element dziel�cy b�dzie teraz porównywany z pozosta�ymi elementami, przekazujemyreferencj� do tego elementu, aby unikn�� wielokrotnego kopiowania . Mo�emy nast�p-nie u�y� funkcji std::partition do podzielenia sekwencji na warto�ci mniejsze odelementu dziel�cego i warto�ci nie mniejsze od tego elementu . Najprostszym spo-sobem okre�lenia kryterium podzia�u jest u�ycie funkcji lambda — aby unikn�� kopio-wania warto�ci elementu dziel�cego, zastosowano tutaj technik� przechwytywania refe-rencji (wi�cej informacji na temat funkcji lambda mo�na znale� w cz��ci A.5 dodatku A).

Funkcja std::partition() przetwarza przekazan� list� i zwraca iterator wskazuj�cypierwszy element, który nie jest mniejszy od warto�ci elementu dziel�cego. Kompletnytyp iteratora mo�e by� do�� d�ugi, zatem w powy�szym kodzie u�yto mechanizmu auto-matycznej identyfikacji typów, aby to kompilator automatycznie okre�li� odpowiednityp (patrz cz��� A.7 dodatku A).

Poniewa� analizowana implementacja udost�pnia interfejs zgodny z zasadami pro-gramowania funkcyjnego, warunkiem rekurencyjnego posortowania obu „po�ówek” listyjest utworzenie dwóch odr�bnych list. Do tego celu mo�emy ponownie u�y� funkcjisplice(), aby skopiowa� warto�ci z listy wej�ciowej (do elementu divide_point) i umie-�ci� na nowej li�cie: lower_part . Reszta warto�ci pozostanie na li�cie wej�ciowej.Obie listy mo�na nast�pnie posortowa� za pomoc� rekurencyjnych wywo�a� funkcjisequential_quick_sort() . U�ycie funkcji std::move() podczas przekazywanialist na wej�ciu rekurencyjnych wywo�a� pozwala unikn�� kopiowania tych struktur(wyniki obu wywo�a� s� kopiowane automatycznie). I wreszcie mo�emy ponownie u�y�funkcji splice() w celu uporz�dkowania list reprezentuj�cych podzbiory elementóworyginalnej struktury. Warto�ci z listy new_higher trafiaj� na koniec listy wynikowej (za element dziel�cy), natomiast warto�ci z listy new_lower s� umieszczane na pocz�tkulisty (przed elementem dziel�cym).

SZYBKIE SORTOWANIE RÓWNOLEG�E W MODELU PROGRAMOWANIA FUNKCYJNEGO

Poniewa� ju� w poprzednim przyk�adzie zastosowano regu�y programowania funkcyj-nego, konwersja tego algorytmu na wersj� równoleg�� (korzystaj�c� z obiektów przy-sz�o�ci) nie jest specjalnie trudna (patrz listing 4.13). Zbiór operacji jest taki sam jakw poprzedniej wersji, tyle �e teraz cz��� tych operacji jest wykonywana równolegle.W tej wersji u�yto implementacji algorytmu sortowania szybkiego ��cz�cej obiekty przy-sz�o�ci i model programowania funkcyjnego.

Jedn� z najwa�niejszych ró�nic dziel�cych obie wersje jest to, �e w wersji wspó�-bie�nej cz��� listy sprzed elementu dziel�cego nie jest sortowana w bie��cym w�tku,tylko w dodatkowym w�tku — w tym celu zastosowano funkcj� std::async() . Druga

4.4. Upraszczanie kodu za pomoc� technik synchronizowania operacji 125

Listing 4.13. Równoleg�e sortowanie szybkie z wykorzystaniem przysz�o�ci

template<typename T>std::list<T> parallel_quick_sort(std::list<T> input){ if(input.empty()) { return input; } std::list<T> result; result.splice(result.begin(),input,input.begin()); T const& pivot=*result.begin();

auto divide_point=std::partition(input.begin(),input.end(), [&](T const& t){return t<pivot;});

std::list<T> lower_part; lower_part.splice(lower_part.end(),input,input.begin(), divide_point);

std::future<std::list<T> > new_lower( std::async(&parallel_quick_sort<T>,std::move(lower_part)));

auto new_higher( parallel_quick_sort(std::move(input)));

result.splice(result.end(),new_higher); result.splice(result.begin(),new_lower.get()); return result;}

cz��� listy jest sortowana tak jak w poprzedniej wersji, a wi�c przy u�yciu bezpo�red-niego wywo�ania rekurencyjnego . Rekurencyjne wywo�anie funkcji parallel_quick_�sort() pozwala wykorzysta� dost�pn� wspó�bie�no�� sprz�tow�. Je�li wywo�aniefunkcji std::async() za ka�dym razem uruchamia nowy w�tek, wystarcz� trzy poziomyrekurencji, aby program zosta� podzielony na osiem w�tków; w przypadku dziesi�ciupoziomów rekurencji (czyli oko�o tysi�ca elementów) program w tej formie uruchomi1024 w�tki (o ile stosowany sprz�t poradzi sobie z tak� liczb�). W razie wykrycia zbytdu�ej liczby uruchomionych zada� (je�li na przyk�ad liczba równolegle realizowanychzada� przekroczy dost�pn� wspó�bie�no�� sprz�tow�) biblioteka mo�e przej�� w trybsynchronicznego uruchamiania nowych zada�. Zadania b�d� wykonywane w w�tkuwywo�uj�cym funkcj� get(), nie w nowym w�tku, zatem program uniknie kosztów prze-kazywania zadania pomi�dzy w�tkami, je�li koszty tej operacji nie s� rekompensowaneprzez wzrost wydajno�ci. Je�li nie przekazano wprost warto�ci std::launch::deferred,uruchamianie nowego w�tku dla ka�dego zadania jest w pe�ni zgodne z za�o�eniamiimplementacji std::async (nawet je�li prowadzi do nadsubskrypcji); podobnie je�linie przekazano warto�ci std::launch::async, najlepszym rozwi�zaniem jest synchro-niczne wykonywanie wszystkich zada�. W przypadku stosowania biblioteki oferuj�cejmechanizmy automatycznego skalowania warto sprawdzi� w dokumentacji, jak te mecha-nizmy b�d� dzia�a�y w kontek�cie tego algorytmu.

Zamiast funkcji std::async() mogliby�my u�y� w�asnej funkcji spawn_task() w roliprostego opakowania szablonu klasy std::packaged_task i klasy std::thread (patrz

126 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

listing 4.14). W takim przypadku nale�a�oby utworzy� obiekt klasy std::packaged_taskdla wyniku wywo�ania funkcji, odczyta� obiekt przysz�o�ci z obiektu zadania, uruchomi�zadanie w odpowiednim w�tku, po czym zwróci� obiekt przysz�o�ci. Takie rozwi�za-nie samo w sobie nie przynios�oby co prawda �adnych korzy�ci (prowadzi�oby raczejdo du�ej nadsubskrypcji), ale mo�e stanowi� punkt wyj�cia dla bardziej zaawansowa-nych implementacji, które b�d� dodawa�y zadania do kolejki w celu przetworzeniaprzez w�tki robocze dost�pne w puli. Zagadnienia zwi�zane z pulami w�tków zostan�omówione w rozdziale 9. Wybór tego kierunku (zamiast stosowania funkcji std::async)jest uzasadniony tylko w przypadku programistów, którzy maj� pe�n� �wiadomo��skutków podejmowanych dzia�a� i chc� mie� pe�n� kontrol� nad sposobem budowypuli w�tków i wykonywania zada�.

Listing 4.14. Prosta implementacja funkcji spawn_task

template<typename F,typename A>std::future<std::result_of<F(A&&)>::type> spawn_task(F&& f,A&& a){ typedef std::result_of<F(A&&)>::type result_type; std::packaged_task<result_type(A&&)> task(std::move(f))); std::future<result_type> res(task.get_future()); std::thread t(std::move(task),std::move(a)); t.detach(); return res;}

Wró�my teraz do funkcji parallel_quick_sort(). Poniewa� do uzyskania listy new_higheru�yli�my bezpo�redniego wywo�ania rekurencyjnego, mo�emy u�y� funkcji splice()tak jak w algorytmie jednow�tkowym . Okazuje si� jednak, �e zmienna new_lowerzawiera obiekt klasy std::future<std::list<T>>, nie list�, zatem przed wywo�aniemfunkcji splice() musimy wywo�a� funkcj� get(), aby uzyska� odpowiedni� warto�� .Wywo�anie w tej formie czeka na zako�czenie zadania wykonywanego w tle, po czymprzenosi wynik do wywo�ania funkcji splice(). Funkcja get() zwraca referencj� dor-warto�ci wyniku, zatem lista mo�e zosta� przeniesiona (wi�cej informacji na tematreferencji do r-warto�ci i operacji przenoszenia mo�na znale� w cz��ci A.1.1 dodatku A).

Nawet je�li przyj��, �e funkcja std::async() w optymalny sposób wykorzystujedost�pn� wspó�bie�no�� sprz�tow�, proponowana implementacja równoleg�ego algorytmusortowania szybkiego wci�� nie jest optymalna. Funkcja std::partition realizuje coprawda znaczn� cz��� zada� zwi�zanych z dzia�aniem tego algorytmu, ale jej wywo�aniema charakter typowo sekwencyjny. Czytelnicy zainteresowani mo�liwie najszybsz�, rów-noleg�� implementacj� powinni si�gn�� po odpowiedni� literatur� akademick�.

Programowanie funkcyjne nie jest jedynym paradygmatem programowania wspó�-bie�nego eliminuj�cym problem wspó�dzielenia zmiennych danych; innym przyk�ademtakiego paradygmatu jest komunikacja procesów sekwencyjnych (ang. CommunicatingSequential Processes — CSP)3, gdzie w�tki s� w za�o�eniu ca�kowicie niezale�ne i nie

3 C.A.R. Hoare, Communicating Sequential Processes, Prentice Hall, 1985. Ksi��ka jest dost�pna za darmo

pod adresem http://www.usingcsp.com/cspbook.pdf.

4.4. Upraszczanie kodu za pomoc� technik synchronizowania operacji 127

operuj� na wspó�dzielonych danych — zamiast tego wymieniaj� komunikaty za po�red-nictwem kana�ów komunikacyjnych. Paradygmat CSP zastosowano w j�zyku progra-mowania Erlang (http://www.erlang.org/) oraz w �rodowisku MPI (od ang. MessagePassing Interface) stosowanym w systemach implementowanych w j�zykach C i C++,które musz� gwarantowa� najwy�sz� wydajno�� (http://www.mpi-forum.org/). Po tym,co ju� napisa�em, jestem pewien, �e wiadomo�� o mo�liwo�ci implementacji tego para-dygmatu tak�e w j�zyku C++ nie b�dzie dla czytelnika �adnym zaskoczeniem — wystar-czy odrobina dyscypliny. Sposób implementacji tego modelu omówi� w nast�pnympunkcie.

4.4.2. Synchronizacja operacji za pomoc� przesy�ania komunikatów

Koncepcja paradygmatu CSP jest prosta — nie istniej� �adne wspó�dzielone dane,a ka�dy w�tek mo�na traktowa� jako zupe�nie niezale�ny byt. Zachowanie w�tku zale�ywy��cznie od komunikatów, które do niego trafiaj�. Ka�dy w�tek jest wi�c swoist�maszyn� stanów, która po otrzymaniu komunikatu aktualizuje swój stan i która mo�e(ale nie musi) wys�a� co najmniej jeden komunikat do pozosta�ych w�tków. Sposób prze-twarzania komunikatu zale�y od stanu pocz�tkowego „maszyny”. Jednym ze sposobówpisania w�tków tego typu jest stworzenie formalnego modelu i implementacja sko�-czonej maszyny stanów, jednak istniej� te� lepsze rozwi�zania — do wyra�enia maszynystanów mo�na u�y� odpowiedniej struktury aplikacji. To, która metoda sprawdza si�lepiej w danym scenariuszu, zale�y od szczegó�owych wymaga� dotycz�cych zachowa�budowanego systemu i od umiej�tno�ci zespo�u programistów. Je�li jednak zdecydu-jemy si� na implementacj� odr�bnych w�tków, sam podzia� na niezale�ne procesymo�e prowadzi� do wyeliminowania wielu komplikacji zwi�zanych ze wspó�bie�nymprzetwarzaniem danych wspó�dzielonych i tym samym u�atwi� programowanie orazograniczy� liczb� b��dów.

Procesy w pe�ni zgodne z paradygmatem CSP nie operuj� na �adnych wspó�dzie-lonych danych, a ca�a komunikacja odbywa si� za po�rednictwem kolejek komunikatów.Poniewa� jednak w�tki j�zyka C++ wspó�dziel� przestrze� adresow�, wymuszenietego wymagania jest niemo�liwe. W tej sytuacji bardzo du�e znaczenie ma dyscyplinaautorów aplikacji i bibliotek, poniewa� to od nas zale�y, czy nasze w�tki b�d� si� odwo-�ywa� do wspó�dzielonych danych. Same kolejki komunikatów oczywi�cie musz� by�wspó�dzielone (w przeciwnym razie w�tki nie mog�yby si� komunikowa�), jednak szcze-gó�y implementacji tych kolejek mo�na opakowa� w ramach bibliotek.

Wyobramy sobie, �e implementujemy kod dla bankomatu. Kod tego systemu musiobs�ugiwa� interakcj� z u�ytkownikiem, czyli osob� wyp�acaj�c� gotówk�, musi komu-nikowa� si� z systemem odpowiedniego banku oraz musi sterowa� fizycznymi urz�-dzeniami odpowiedzialnymi za akceptacj� karty, wy�wietlanie stosownych komunika-tów, obs�ug� klawiatury, wyp�at� pieni�dzy i zwracanie karty.

Jednym ze sposobów obs�ugi wszystkich tych zada� jest podzielenie kodu na trzyniezale�ne w�tki: jeden obs�uguj�cy fizyczne urz�dzenia, drugi implementuj�cy logik�samego bankomatu i trzeci odpowiedzialny za komunikacj� z bankiem. W�tki mog� si�komunikowa� wy��cznie poprzez przekazywanie komunikatów (nie poprzez wspó�-dzielenie jakichkolwiek danych). Na przyk�ad w�tek obs�uguj�cy fizyczne urz�dzeniamóg�by wys�a� do w�tku logiki bankomatu komunikat o w�o�eniu karty lub naci�ni�ciu

128 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

przycisku przez u�ytkownika, natomiast w�tek logiki móg�by wys�a� do w�tku obs�u-guj�cego fizyczne urz�dzenia komunikat okre�laj�cy kwot� do wyp�acenia.

Jednym ze sposobów modelowania logiki bankomatu jest opracowanie maszynystanów. W ka�dym stanie w�tek oczekuje na okre�lony komunikat, który jest nast�p-nie odpowiednio przetwarzany. W wyniku przetwarzania tego komunikatu w�tek mo�eprzej�� w nowy stan i kontynuowa� ca�y cykl. Stany sk�adaj�ce si� na t� prost� imple-mentacj� pokazano na rysunku 4.3. W tej uproszczonej implementacji system oczekujena umieszczenie karty w bankomacie. Po w�o�eniu karty system czeka, a� u�ytkownikwpisze kod PIN, naciskaj�c kolejno cyfry tego kodu. U�ytkownik mo�e usun�� ostat-ni� wpisan� cyfr�. Po wpisaniu odpowiedniej liczby cyfr system weryfikuje kod PIN.Je�li PIN jest nieprawid�owy, cykl dzia�ania ko�czy si� — bankomat zwraca kart� i prze-chodzi w stan oczekiwania na wsuni�cie karty przez klienta. Je�li kod PIN jest prawi-d�owy, system czeka na anulowanie transakcji albo na wybór kwoty do wyp�acenia.W razie anulowania transakcji cykl pracy bankomatu ko�czy si� i urz�dzenie zwracakart�. Je�li klient wybra� kwot�, przed wyp�aceniem gotówki system czeka na potwier-dzenie ze strony banku, po czym albo wyp�aca gotówk�, albo wy�wietla komunikat„brak �rodków” i (niezale�nie od wyniku weryfikacji stanu konta) wysuwa kart�. Sys-temy prawdziwych bankomatów s� oczywi�cie bardziej skomplikowane, jednak opisanapowy�ej maszyna stanów dobrze ilustruje istot� tego rozwi�zania.

Rysunek 4.3. Prosty model maszyny stanów dla bankomatu

Po zaprojektowaniu maszyny stanów dla logiki bankomatu mo�emy przyst�pi� do imple-mentacji tego rozwi�zania w formie klasy, która b�dzie definiowa�a po jednej funkcjisk�adowej dla ka�dego stanu. Ka�da funkcja sk�adowa mo�e czeka� na okre�lony zbiórkomunikatów przychodz�cych i odpowiednio obs�ugiwa� te komunikaty (obs�uga mo�epolega� na przechodzeniu do innego stanu). Ka�dy typ komunikatów jest reprezentowany

4.4. Upraszczanie kodu za pomoc� technik synchronizowania operacji 129

przez odr�bn� struktur�. Na listingu 4.15 pokazano fragment prostej implementacjilogiki takiego systemu, w tym g�ówn� p�tl� oraz implementacj� pierwszego stanu, w któ-rym system oczekuje na w�o�enie karty.

Listing 4.15. Prosta implementacja klasy logiki systemu bankomatu

struct card_inserted{ std::string account;};class atm{ messaging::receiver incoming; messaging::sender bank; messaging::sender interface_hardware; void (atm::*state)();

std::string account; std::string pin;

void waiting_for_card() { interface_hardware.send(display_enter_card()); incoming.wait() .handle<card_inserted>( [&](card_inserted const& msg) { account=msg.account; pin=""; interface_hardware.send(display_enter_pin()); state=&atm::getting_pin; } ); } void getting_pin();public: void run() { state=&atm::waiting_for_card; try { for(;;) { (this->*state)(); } } catch(messaging::close_queue const&) { } }};

Jak wida�, wszystkie niezb�dne operacje zwi�zane z synchronizacj� przekazywaniakomunikatów zosta�y ukryte w odpowiedniej bibliotece (implementacja tej bibliotekizostanie pokazana w dodatku C wraz z kompletnym kodem tego przyk�adu).

130 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

Jak ju� wspomnia�em, opisana tutaj implementacja jest mocno uproszczona w sto-sunku do logiki obowi�zuj�cej w prawdziwych bankomatach, jednak przyk�ad w tejformie wystarczy do zrozumienia stylu programowania na bazie przekazywania komu-nikatów. Nie musimy traci� czasu na projektowanie synchronizacji i rozwi�zywanieproblemów zwi�zanych z przetwarzaniem wspó�bie�nym — wystarczy ustali�, jakiekomunikaty mog� by� odbierane i przetwarzane na poszczególnych etapach oraz którekomunikaty nale�y wysy�a�. Maszyna stanów logiki bankomatu jest przetwarzana przezjeden w�tek; pozosta�e elementy systemu, jak interfejs ��cz�cy si� z bankiem czy interfejsterminala, s� obs�ugiwane przez odr�bne w�tki. Ten styl projektowania oprogramowaniaokre�la si� mianem modelu aktorów — w systemie istnieje wiele odr�bnych aktorów(ka�dy dzia�a w osobnym w�tku), które wymieniaj� pomi�dzy sob� komunikaty niezb�dnedo realizacji swoich zada�. W modelu aktorów nie istnieje wspó�dzielony stan (wyj�tkiemjest mechanizm potrzebny do bezpo�redniego przekazywania komunikatów).

Dzia�anie programu rozpoczyna si� od funkcji sk�adowej run() , która ustawiastan pocz�tkowy, czyli waiting_for_card , po czym wielokrotnie wywo�uje funkcj�sk�adow� reprezentuj�c� bie��cy stan (niezale�nie od tego, który to stan). Funkcjestanów maj� posta� prostych funkcji sk�adowych klasy atm. Tak�e funkcja stanuwaiting_for_card jest do�� prosta — jej dzia�anie ogranicza si� do wys�ania komu-nikatu do interfejsu w celu wy�wietlenia na ekranie tekstu Czekam na kart� ; zarazpotem funkcja rozpoczyna oczekiwanie na komunikat do obs�u�enia . Jedynym rodza-jem komunikatów, który mo�e by� obs�ugiwany w tej cz��ci kodu, jest komunikatcard_inserted. Do jego obs�ugi u�ywamy funkcji lambda . Na wej�ciu funkcji handlemo�na przekaza� dowoln� funkcj� lub dowolny obiekt funkcji, jednak najprostszymrozwi�zaniem jest u�ycie funkcji lambda. �atwo zauwa�y�, �e wywo�anie funkcji handle()znalaz�o si� w �a�cuchu obejmuj�cym wywo�anie funkcji wait(), zatem w razie otrzy-mania komunikatu, który nie pasuje do wskazanego typu, w�tek zignoruje ten komu-nikat i b�dzie dalej czeka� na komunikat w�a�ciwego typu.

Sama funkcja lambda zapisuje numer konta w zmiennej sk�adowej, zeruje bie��cykod PIN, wysy�a komunikat do sprz�tu odpowiedzialnego za obs�ug� interfejsu ban-komatu, aby wy�wietli� pro�b� o podanie kodu PIN, po czym przechodzi w stan po-bierania tego kodu. Po zako�czeniu dzia�ania przez funkcj� obs�uguj�c� komunikatfunkcja stanu zwraca sterowanie, a g�ówna p�tla programu wywo�uje funkcj� nowegostanu .

Funkcja stanu getting_pin jest nieco bardziej skomplikowana, poniewa� mo�e obs�u-giwa� trzy ró�ne typy komunikatów (patrz rysunek 4.3). Kod tej funkcji pokazano nalistingu 4.16.

Listing 4.16. Funkcja stanu getting_pin na potrzeby prostej implementacji systemubankomatu

void atm::getting_pin(){ incoming.wait() .handle<digit_pressed>( [&](digit_pressed const& msg) { unsigned const pin_length=4; pin+=msg.digit; if(pin.length()==pin_length)

4.5. Podsumowanie 131

{ bank.send(verify_pin(account,pin,incoming)); state=&atm::verifying_pin; } } ) .handle<clear_last_pressed>( [&](clear_last_pressed const& msg) { if(!pin.empty()) { pin.resize(pin.length()-1); } } ) .handle<cancel_pressed>( [&](cancel_pressed const& msg) { state=&atm::done_processing; } );}

Tym razem funkcja musi przetwarza� trzy ró�ne typy komunikatów, zatem �a�cuchwywo�ania funkcji wait() obejmuje a� trzy wywo�ania funkcji handle() . Ka�dewywo�anie funkcji handle() okre�la typ komunikatu w formie parametru szablonu, poczym przekazuje funkcj� lambda, która otrzymuje na wej�ciu komunikat okre�lonegotypu. Poniewa� wszystkie te wywo�ania umieszczono w jednym �a�cuchu wywo�a�,implementacja funkcji wait() „wie”, �e czeka na komunikat digit_pressed, clear_�last_pressed lub cancel_pressed. Komunikaty wszystkich innych typów b�d� (takjak wcze�niej) ignorowane.

W tym przypadku otrzymanie komunikatu nie musi prowadzi� do zmiany stanu.Je�li na przyk�ad w�tek otrzyma komunikat digit_pressed i je�li wpisana cyfra nie b�dzieostatni� cyfr� kodu, wystarczy t� cyfr� dopisa� do �a�cucha pin. G�ówna p�tla nalistingu 4.15 ponownie wywo�a funkcj� getting_pin(), aby czeka� na nast�pn� cyfr�(b�d wyzerowa� kod PIN lub anulowa� ca�� operacj�).

Opisana procedura jest zgodna ze schematem pokazanym na rysunku 4.3. Ka�dystan przedstawiony na tym rysunku jest implementowany przez inn� funkcj� sk�adow�,która czeka na odpowiednie komunikaty i na tej podstawie aktualizuje stan systemu.

Jak wida�, opisany styl programowania mo�e znacznie upro�ci� zadanie projekto-wania systemu wspó�bie�nego, poniewa� ka�dy w�tek mo�na traktowa� ca�kowicieniezale�nie od pozosta�ych. W opisanym modelu wiele w�tków ma za zadanie oddzie-lanie zagadnie� i jako takie wymagaj� od projektanta jasnych decyzji o podziale zada�pomi�dzy w�tki.

4.5. Podsumowanie

Synchronizacja operacji pomi�dzy w�tkami jest wa�nym aspektem pisania aplikacjistosuj�cej techniki przetwarzania wspó�bie�nego — w razie braku synchronizacji w�tkidzia�a�yby ca�kowicie niezale�nie, zatem równie dobrze mog�yby mie� posta� odr�bnychaplikacji uruchamianych w formie pewnej grupy (z racji pokrewnych zada�). W tym

132 ROZDZIA� 4. Synchronizacja wspó�bie�nych operacji

rozdziale omówi�em rozmaite sposoby synchronizacji operacji, w tym podstawowezmienne warunkowe, przysz�o�ci, obietnice i opakowywane zadania. Opisa�em te� spo-soby rozwi�zywania problemów zwi�zanych z synchronizacj�, w szczególno�ci pro-gramowanie funkcyjne, gdzie ka�de zadanie generuje wynik wy��cznie na podstawiedanych wej�ciowych (nie uwzgl�dnia zewn�trznego �rodowiska), oraz przekazywaniekomunikatów, gdzie komunikacja pomi�dzy w�tkami odbywa si� za po�rednictwemasynchronicznych komunikatów (wysy�anych przy u�yciu podsystemu przekazywaniakomunikatów).

Skoro omówi�em ju� wiele wysokopoziomowych rozwi�za� dost�pnych w j�zykuC++, czas przyjrze� si� odpowiednim elementom niskopoziomowym, dzi�ki którymopisane mechanizmy mog� funkcjonowa� — w nast�pnym rozdziale skoncentrujemy si�na modelu pami�ci j�zyka C++ i operacjach atomowych.

Skorowidz

Aaccumulate_block, 293, 294add_to_list(), 61add_to_reclaim_list(), 239aktywne oczekiwanie, 261algorytm sortowania szybkiego, 330analiza kodu, 357

próba szczegó�owego wyja�nienia komu�, 358przegl�danie kodu, 357pytania dotycz�ce przegl�danego kodu, 358

asynchroniczne zadanie, 104

Bbariera, 316bariery pami�ci, Patrz ogrodzeniabiblioteki j�zyka C++, 29

ACE, 29Boost, 29Boost Thread Library, 30C++ Thread Library, 30efektywno�� klas, 30mechanizm przysz�o�ci, 103okres, 117przysz�o�ci unikatowe, 103przysz�o�ci wspó�dzielone, 103punkt w czasie, 118RAII, 29standardowa, 31typy wywo�ywalne, 36

wolne funkcje, 150zegar, 115

blokady wiruj�ce, 221b��dzenie, Patrz uwi�zienieBoost Thread Library, 30buffer, 44

CC++ Thread Library, 30clear(), 138, 141compare_exchange_strong(), 140, 144, 236, 248,

263compare_exchange_weak(), 140, 144, 225, 247

Ddata.push(), 188data_cond.notify_one(), 191definicja klasy stosu, 68delete_nodes_with_no_hazards(), 238, 240detach(), 38, 42dispatch(), 405do_something(), 63do_sort(), 275, 333done(), 110Double-Checked Locking, 85drzewo binarne, 209dyndaj�cy wskanik, 226

564 Skorowidz

Eempty(), 64, 102, 188exchange(), 140, 143

Ffa�szywe wspó�dzielenie, 284, 287fetch_add(), 140, 146, 249fetch_and(), 147fetch_or(), 140, 147fetch_sub(), 146, 176fetch_xor(), 147find_entry_for(), 212find_first_if(), 217for_each(), 213, 216frameworki aplikacji, 29

MFC, 29free_external_counter(), 260front(), 98funkcja lambda, 122funkcja pocz�tkowa, 33funkcje

accumulate_block, 293, 294add_to_list(), 61add_to_reclaim_list(), 239clear(), 138, 141compare_exchange_strong(), 140, 144, 236,

248, 263compare_exchange_weak(), 140, 144, 225, 247constexpr, 381

wymagania, 385data.push(), 188data_cond.notify_one(), 191delete_nodes_with_no_hazards(), 238, 240detach(), 38, 42dispatch(), 405do_something(), 63do_sort(), 275, 333domy�lne, 377

dokumentacja, 378wymuszanie generowania funkcji, 378wymuszenie deklarowania konstruktora

kopiuj�cego, 378wymuszenie generowania destruktora

wirtualnego, 378zmiana dost�pno�ci funkcji, 378

done(), 110empty(), 64, 102, 188exchange(), 140, 143fetch_add(), 140, 146, 249

fetch_and(), 147fetch_or(), 140, 147fetch_sub(), 146, 176fetch_xor(), 147find_entry_for(), 212find_first_if(), 217foo<int&>(), 375for_each(), 213, 216free_external_counter(), 260front(), 98get(), 125get_event(), 301get_future(), 106, 114get_hazard_pointer_for_current_thread(), 234,

236get_id(), 52get_lock(), 80get_tail(), 200getting_pin(), 130handle(), 130, 407head.get(), 197hello(), 33interrupt(), 340, 342, 349interruptible_wait(), 343, 348interruption_point(), 341, 344, 351is_lock_free(), 138joinable(), 295lambda, 386

wyra�enie lambda, 386z konstrukcj� rozpoczynaj�c�, 388

list_contains(), 61load(), 140, 143lock(), 60, 79, 80, 142main(), 33, 36memcmp(), 148memcpy(), 148my_thread, 37my_x.do_lengthy_work(), 45native_handle(), 32niesk�adowe, 150notify_one(), 96now(), 116open_connection(), 87parallel_accumulate(), 291, 296, 329parallel_quick_sort(), 126, 275pocz�tkowa, 33pop(), 64, 188, 226, 228, 235, 245, 247, 254,

257, 261pop_head(), 205pop_task_from_other_thread_queue(), 339process(), 82, 301

Skorowidz 565

process_connections(), 110process_data(), 81push(), 64, 100, 191, 197, 201, 225, 229, 247,

254, 255, 261, 337push_front(), 216reclaim_later(), 236, 238, 239remove_if(), 217run(), 130run_pending_task(), 331, 335send_data(), 87sequential_quick_sort(), 124set(), 343set_condition_variable(), 344set_exception(), 111set_new_tail(), 264share(), 114size(), 64sleep_for(), 120sleep_until(), 120some_function, 47spawn_task(), 125splice(), 124square_root(), 111std::accumulate, 49std::async(), 104, 125, 273, 281, 297std::atomic_compare_exchange_strong, 469std::atomic_compare_exchange_strong_explicit,

469std::atomic_compare_exchange_weak, 470std::atomic_compare_exchange_weak_explicit,

471std::atomic_exchange, 467std::atomic_exchange_explicit, 467std::atomic_fetch_add, 476, 486std::atomic_fetch_add_explicit, 476, 486std::atomic_fetch_and, 478std::atomic_fetch_and_explicit, 479std::atomic_fetch_or, 479std::atomic_fetch_or_explicit, 480std::atomic_fetch_sub, 477, 487std::atomic_fetch_sub_explicit, 478, 487std::atomic_fetch_xor, 480std::atomic_fetch_xor_explicit, 481std::atomic_flag_clear, 459std::atomic_flag_clear_explicit, 460std::atomic_flag_test_and_set, 459std::atomic_flag_test_and_set_explicit, 459std::atomic_flag::clear, 459std::atomic_flag::test_and_set, 458std::atomic_init, 463std::atomic_is_lock_free, 464

std::atomic_load, 465std::atomic_load_explicit, 465std::atomic_signal_fence(), 457std::atomic_store, 466std::atomic_store_explicit, 466std::atomic_thread_fence(), 456std::atomic<t*>::fetch_add, 486std::atomic<t*>::fetch_sub, 487std::atomic<typ-

ca�kowitoliczbowy>::fetch_add, 476std::atomic<typ-

ca�kowitoliczbowy>::fetch_and, 478std::atomic<typ-ca�kowitoliczbowy>::fetch_or,

479std::atomic<typ-

ca�kowitoliczbowy>::fetch_sub, 477std::atomic<typ-

ca�kowitoliczbowy>::fetch_xor, 480std::atomic::compare_exchange_strong, 468std::atomic::compare_exchange_weak, 469std::atomic::exchange, 467std::atomic::is_lock_free, 464std::atomic::load, 464std::atomic::store, 466std::bind(), 45, 333std::call_once, 86std::chrono::duration_cast, 428std::chrono::duration::count, 423std::chrono::duration::max, 426std::chrono::duration::min, 426std::chrono::duration::zero, 425std::chrono::steady_clock::now, 434std::chrono::system_clock::from_time_t, 433std::chrono::system_clock::now, 432std::chrono::system_clock::to_time_t, 433std::chrono::time_point::max, 431std::chrono::time_point::min, 431std::chrono::time_point::time_since_epoch, 430std::condition_variable_any::notify_all, 446std::condition_variable_any::notify_one, 446std::condition_variable_any::wait, 447std::condition_variable_any::wait_for, 448std::condition_variable_any::wait_until, 450std::condition_variable::notify_all, 437std::condition_variable::notify_one, 437std::condition_variable::wait, 344, 438std::condition_variable::wait_for, 439std::condition_variable::wait_until, 441std::copy_exception(), 112std::current_exception(), 112std::find, 306

566 Skorowidz

funkcjestd::for_each, 51, 304std::future::get, 494std::future::share, 492std::future::valid, 492std::future::wait, 493std::future::wait_for, 493std::future::wait_until, 494std::kill_dependency(), 174std::lock(), 72std::move(), 46, 124, 329, 333std::mutex::lock, 516std::mutex::try_lock, 516std::mutex::unlock, 517std::notify_all_at_thread_exit, 443std::packaged_task::get_future, 504std::packaged_task::make_ready_at_thread_exit,

506std::packaged_task::reset, 505std::packaged_task::swap, 504std::packaged_task::valid, 505std::partial_sum, 312std::partition(), 124std::promise::get_future, 510std::promise::set_exception, 512std::promise::set_exception_at_thread_exit,

512std::promise::set_value, 510std::promise::set_value_at_thread_exit, 511std::promise::swap, 509std::recursive_mutex::lock, 519std::recursive_mutex::try_lock, 519std::recursive_mutex::unlock, 519std::recursive_timed_mutex::lock, 526std::recursive_timed_mutex::try_lock, 526std::recursive_timed_mutex::try_lock_for, 526std::recursive_timed_mutex::try_lock_until,

527std::recursive_timed_mutex::unlock, 528std::ref, 45std::shared_future::get, 500std::shared_future::valid, 498std::shared_future::wait, 498std::shared_future::wait_for, 499std::shared_future::wait_until, 499std::terminate(), 37, 349std::this_thread::get_id, 52, 558std::this_thread::sleep_for, 559std::this_thread::sleep_until, 559std::this_thread::yield, 559

std::thread::detach, 557std::thread::get_id, 558std::thread::hardware_concurrency, 49, 273,

276, 280, 324, 558std::thread::join, 557std::thread::joinable, 556std::thread::native_handle, 553std::thread::swap, 556std::timed_mutex::lock, 521std::timed_mutex::try_lock, 522std::timed_mutex::try_lock_for, 522std::timed_mutex::try_lock_until, 523std::timed_mutex::unlock, 524std::unique_lock::lock, 536std::unique_lock::mutex, 539std::unique_lock::operator, 539std::unique_lock::owns_lock, 539std::unique_lock::release, 539std::unique_lock::swap, 535, 536std::unique_lock::try_lock, 536std::unique_lock::try_lock_for, 537std::unique_lock::try_lock_until, 538std::unique_lock::unlock, 537store(), 140, 143, 177submit(), 326, 329, 335swap(), 64test_and_set(), 138, 141thread_a(), 76thread_b(), 76time_since_epoch(), 118top(), 64try_lock(), 76, 80try_lock_for(), 120try_lock_until(), 120try_pop(), 98, 191, 197, 200, 202, 337try_reclaim(), 230try_steal(), 337trywialne, 379unlock(), 60, 80, 348update_data_for_widget, 44wait(), 96, 102, 318, 344, 348, 403wait_and_dispatch(), 405, 407wait_and_pop(), 98, 191, 202, 204wait_for(), 115, 120, 344wait_for_data(), 205wait_until(), 115worker_thread(), 326, 335zegar::now(), 116

Skorowidz 567

Gget(), 125get_event(), 301get_future(), 106, 114get_hazard_pointer_for_current_thread(), 234, 236get_id(), 52get_lock(), 80get_tail(), 200getting_pin(), 130

Hhandle(), 130, 407head.get(), 197hello(), 33

Iidentyfikowanie w�tków, 52

identyfikatory w�tków, 52interrupt(), 340, 342, 349interruptible_wait(), 343, 348interruption_point(), 341, 344, 351is_lock_free(), 138iteratory jednokrotnego przebiegu, 52iteratory post�puj�ce, 52

Jj�zyk C++, 19

automatyczne okre�lanie typu zmiennej, 395biblioteka operacji atomowych, 31biblioteka standardowa, 31biblioteka w�tków, 30biblioteki, 29efektywno�� klas, 30frameworki aplikacji, 29mechanizm deklarowania funkcji

jako usuni�tej, 376mechanizm przysz�o�ci, 103miejsce w pami�ci, 134model pami�ci, 133muteks, 60obiekty, 134obs�uga wspó�bie�no�ci, 28, 30operacje atomowe, 137porz�dek modyfikacji, 136RAII, 29referencje do r-warto�ci, 371

semantyka przenoszenia danych, 372

szablony zmiennoargumentowe, 391paczka parametrów, 392rozwini�cie paczki, 392

typy definiowane przez u�ytkownika, 382inicjalizacja statyczna, 384

warstwy abstrakcji, 30wyra�enia sta�e, 381

zastosowania, 381wyra�enie lambda, 37wy�cig danych, 58zmienne lokalne w�tków, 396

join(), 39joinable(), 295

Kklasy

dispatcher, 404receiver, 403scoped_thread, 48sender, 402std::thread, 36std::atomic_flag, 457std::chrono::high_resolution_clock, 116std::chrono::steady_clock, 116, 433std::chrono::system_clock, 116, 431std::condition_variable, 95, 436std::condition_variable_any, 95, 444std::future<>, 103std::mutex, 60, 515std::once_flag, 541std::recursive_mutex, 90, 517std::recursive_timed_mutex, 524std::shared_future<>, 103std::thread, 549std::thread::id, 550std::timed_mutex, 520thread_guard, 48thread_pool, 331

komunikacja procesów sekwencyjnych, 126, 127maszyna stanów, 127, 128model aktorów, 130model maszyny stanów, 128

konstruktor domy�lny, 52konstruktor przenosz�cy, 45kontenery

std::map<>, 207std::multimap<>, 207std::queue<>, 97std::stack, 64, 66std::unordered_map<>, 207std::unordered_multimap<>, 207

568 Skorowidz

Lleniwa inicjalizacja, 85linie pami�ci podr�cznej, 284list_contains(), 61load(), 140, 143lock(), 60, 79, 80, 142l-warto��, 80

Mmain(), 33, 36mechanizm przysz�o�ci, 52, 102, 103

asynchroniczne zadanie, 104obietnice, 109oczekiwanie na wiele w�tków, 112przysz�o�ci unikatowe, 103przysz�o�ci wspó�dzielone, 103wi�zanie zadania z przysz�o�ci�, 106wynik oblicze� wykonywanych w tle, 103wy�cig danych, 112zapisywanie wyj�tku, 111

memcmp(), 148memcpy(), 148model aktorów, 130model pami�ci j�zyka C++, 133

analiza podstawowych cech strukturalnych,134

mechanizmy przetwarzania wspó�bie�nego,134

miejsce w pami�ci, 134modele porz�dkowania spójnego

niesekwencyjnie, 159niezdefiniowane zachowanie, 136obiekty, 134ogrodzenia, 178operacje atomowe, 136podzia� struktury, 135porz�dek modyfikacji, 136porz�dkowania pami�ci, 155porz�dkowanie poprzez wzajemne

wykluczanie, 155, 166operacje uzyskiwania, 166operacje zwalniania, 166

porz�dkowanie spójne sekwencyjnie, 155, 156porz�dkowanie z�agodzone, 155, 160

wyja�nienie porz�dkowania, 164relacja poprzedzania, 152, 154

poprzedzanie wed�ug zale�no�ci, 173przechodnio��, 170relacja wprowadzania zale�no�ci, 173

relacja synchronizacji, 152sekwencja zwalniania, 175

wy�cig danych, 136muteks, 59, 60, 220

blokowanie, 60blokowanie rekurencyjne, 90czytelników-pisarzy, 88elastyczne blokowanie, 79hierarchiczny, 77l-warto��, 80niezdefiniowane zachowanie, 90ochrona listy, 61odblokowywanie, 60przenoszenie w�asno�ci, 80rekurencyjny, 90r-warto��, 80stosowanie w j�zyku C++, 60szczegó�owo�� blokady, 82wiruj�cy, 142zakleszczenie, 71

my_thread, 37my_thread.detach(), 39my_thread.join(), 39my_x.do_lengthy_work(), 45

Nnadsubskrypcja, 51, 280, 285native_handle(), 32niezdefiniowane zachowanie, 58, 86, 90, 136niezmienniki, 56, 355niskie wspó�zawodnictwo, 282notify_one(), 96now(), 116

Oobiekty przysz�o�ci, 122ochrona wspó�dzielonych danych, 56

blokowanie rekurencyjne, 90definicja klasy kolejki, 100, 190definicja klasy stosu, 68, 187Double-Checked Locking, 85elastyczne blokowanie muteksu, 79hierarchia blokad, 75implementacja listy z obs�ug� iteracji, 214implementacja tablicy wyszukiwania, 210jednow�tkowa implementacja kolejki, 195kolejka z mechanizmami blokowania i

oczekiwania, 203kolejka ze szczegó�owymi blokadami, 198

Skorowidz 569

kolejka ze sztucznym w�z�em, 196leniwa inicjalizacja, 85lista jednokierunkowa, 194metody zapewniania bezpiecze�stwa, 185muteks, 59, 60muteks czytelników-pisarzy, 88niezdefiniowane zachowanie, 58niezmienniki, 56ochrona listy, 61pami�� transakcyjna, 59programowanie bez blokad, 59przekazywanie referencji, 66przenoszenie w�asno�ci muteksu, 80stosowanie konstruktora, 67struktury danych bez blokad, 220sytuacja wy�cigu, 58szczegó�owo�� blokady, 82szeregowanie, 185tablica wyszukiwania, 207unikanie zakleszcze�, 71wykrywanie sytuacji wy�cigu, 63zakleszczenie, 71zwracanie wskanika, 67

ogrodzenia, 178open_connection(), 87operacje atomowe, 136, 137

funkcje niesk�adowe, 150g�ówny szablon, 147modele porz�dkowania spójnego

niesekwencyjnie, 159ogrodzenia, 178operacje dost�pne dla typów atomowych, 149operacje �adowania (odczytu), 140, 143operacje odczyt-modyfikacja-zapis, 140, 141, 143,operacje porównania-wymiany, 144operacje wymiany i dodania, 146operacje zapisu, 140, 141, 143porównywanie bitowe, 148porz�dkowanie pami�ci, 155porz�dkowanie poprzez wzajemne

wykluczanie, 155, 166operacje uzyskiwania, 166operacje zwalniania, 166

porz�dkowanie spójne sekwencyjnie, 155, 156porz�dkowanie z�agodzone, 155, 160

wyja�nienie porz�dkowania, 164pozorny b��d, 144relacja poprzedzania, 152, 154

poprzedzanie wed�ug zale�no�ci, 173przechodnio��, 170relacja wprowadzania zale�no�ci, 173

relacja synchronizacji, 152sekwencja zwalniania, 175

rozkazy porównywania i wymiany podwójnegos�owa, 149

sekwencja zwalniania zasobów, 147standardowe definicje typów atomowych, 139standardowe typy atomowe, 138stos bez blokad, 250tworzenie typów niestandardowych, 147wolne funkcje, 150

operator przypisania z przenoszeniem, 45

Ppami�� transakcyjna, 59paradygmat CSP, Patrz komunikacja procesów

sekwencyjnychparallel_accumulate(), 291, 296, 329parallel_quick_sort(), 126, 275ping-pong buforów, 282podzia� zagadnie�, 25pop(), 64, 188, 226, 228, 235, 245, 247, 254, 257,

261pop_head(), 205pop_task_from_other_thread_queue(), 339porównywanie bitowe, 148potok, 278pozorne budzenie, 97prawo Amdahla, 299problem ABA, 266process(), 82, 301process_connections(), 110process_data(), 81procesy demonów, 42programowanie bez blokad, 59programowanie funkcyjne, 122

algorytm sortowania szybkiego, 122funkcja lambda, 122obiekty przysz�o�ci, 122rekurencyjne sortowanie, 123równoleg�e sortowanie szybkie, 125wnioskowanie typów zmiennych, 122

projektowanie uniwersalnej kolejki, 97projektowanie wspó�bie�nego kodu, 269

bezpiecze�stwo wyj�tków, 291, 293, 297algorytmy równoleg�e, 291

czynniki wp�ywaj�ce na wydajno�� kodu, 279fa�szywe wspó�dzielenie, 284liczba procesorów, 280nadsubskrypcja, 280, 285niskie wspó�zawodnictwo, 282

570 Skorowidz

projektowanie wspó�bie�nego koduping–pong buforów, 282prze��czanie zada�, 285wspó�zawodnictwo o dane, 281wybór najlepszego algorytmu, 281wysokie wspó�zawodnictwo, 282zmiana elementu danych, 280

fa�szywe wspó�dzielenie, 287�atwo�� testowania, 361

eliminacja wspó�bie�no�ci, 362warunki struktury kodu, 361

mno�enie macierzy, 287podzia� elementów tablicy, 287potok, 278praktyka, 303

bariera, 316implementacja równoleg�ego algorytmu

wyszukiwania, 307równoleg�a implementacja funkcji

partial_sum, 319równoleg�a wersja funkcji std::for_each, 304równoleg�e obliczanie sum cz��ciowych, 313

prawo Amdahla, 299pula w�tków, 276s�siedztwo danych, 287skalowalno��, 291, 298techniki dzielenia pracy pomi�dzy w�tki, 270

dzielenie przed rozpocz�ciem przetwarzania,271

dzielenie sekwencji zada�, 278dzielenie wed�ug typu zadania, 276podzia� s�siaduj�cych fragmentów danych,

272rekurencyjne dzielenie danych, 272, 273równoleg�y algorytm sortowania szybkiego,

273sposób izolowania zagadnie�, 277

techniki przetwarzania równoleg�ego, 301ukrywanie opónie�, 300wspó�zawodnictwo, 286wzorce dost�pu do danych, 289

przekazywanie argumentów do funkcji w�tku, 43konstruktor przenosz�cy, 45kopiowane, 43operator przypisania z przenoszeniem, 45przenoszenie, 45

prze��czanie kontekstu, 21prze��czanie zada�, 21, 22przenoszenie w�asno�ci w�tku, 46, 47przetwarzanie wspó�bie�ne, 29

bezpiecze�stwo, 184

definicja klasy kolejki, 190definicja klasy stosu, 68implementacja listy z obs�ug� iteracji, 214implementacja tablicy wyszukiwania, 210kolejka z mechanizmami blokowania i

oczekiwania, 203kolejka ze szczegó�owymi blokadami, 198mechanizmy przysz�o�ci, 102mi�dzyw�tkowa relacja poprzedzania, 155niechciane blokowanie, 354

oczekiwanie na zewn�trzne dane wej�ciowe,355

uwi�zienie, 354zakleszczenie, 354

oczekiwanie na zdarzenie, 94stos bez blokad, 227, 250synchronizacja operacji, 93sytuacje wy�cigu, 355

naruszone niezmienniki, 355problemy zwi�zane z czasem �ycia, 356wy�cig danych, 355

tablica mieszaj�ca, 209tablica wyszukiwania, 207techniki lokalizacji b��dów, 357

analiza kodu, 357próba szczegó�owego wyja�nienia komu�, 358pytania dotycz�ce przegl�danego kodu, 358testowanie wspó�bie�nego kodu, 359

wspó�dzielenie danych przez w�tki, 55wywo�anie blokuj�ce z limitem czasowym, 115zarz�dzanie w�tkami, 36

identyfikowanie w�tków, 52mechanizm przysz�o�ci, 52oczekiwanie na zako�czenie w�tku, 39oczekiwanie w razie wyst�pienia wyj�tku, 39od��czenie w�tku, 41przekazywanie argumentów do funkcji w�tku,

43przenoszenie w�asno�ci w�tku, 46uruchamianie w�tków w tle, 42uruchamianie w�tku, 36w�tki demonów, 42wybór liczby w�tków, 49

zmienna warunkowa, 95pula w�tków, 106, 276, 324

wi�zanie zadania z przysz�o�ci�, 106push(), 64, 100, 191, 197, 201, 225, 229, 247, 254,

255, 261, 337push_front(), 216

Skorowidz 571

RRAII, 29reclaim_later(), 236, 238, 239referencje do r-warto�ci, 371

semantyka przenoszenia danych, 372konstruktor kopiuj�cy, 374konstruktor przenosz�cy, 374

relacja poprzedzania, 154poprzedzanie wed�ug zale�no�ci, 173przechodnio��, 170relacja wprowadzania zale�no�ci, 173

relacja synchronizacji, 152sekwencja zwalniania, 175

remove_if(), 217Resource Acquisition Is Initialization, Patrz RAIIRIAA, 40rozkazy porównywania i wymiany podwójnego

s�owa, 149równoleg�o��, Patrz zrównoleglanie zada�równoleg�o�� danych, 26run(), 130run_pending_task(), 331, 335r-warto��, 80

Ss�siedztwo danych, 287scoped_thread, 48sekwencja zwalniania, 175send_data(), 87sequential_quick_sort(), 124set(), 343set_condition_variable(), 344set_exception(), 111set_new_tail(), 264share(), 114size(), 64skalowalno��, 298, 369sleep_for(), 120sleep_until(), 120some_function, 47spawn_task(), 125splice(), 124square_root(), 111std::accumulate, 49std::async, 104, 125, 273, 281, 297, 513std::atomic, 138, 140, 147, 460std::atomic_compare_exchange_strong, 469std::atomic_compare_exchange_strong_explicit,

469

std::atomic_compare_exchange_weak, 470std::atomic_compare_exchange_weak_explicit, 471std::atomic_exchange, 467std::atomic_exchange_explicit, 467std::atomic_fetch_add, 476, 486std::atomic_fetch_add_explicit, 476, 486std::atomic_fetch_and, 478std::atomic_fetch_and_explicit, 479std::atomic_fetch_or, 479std::atomic_fetch_or_explicit, 480std::atomic_fetch_sub, 477, 487std::atomic_fetch_sub_explicit, 478, 487std::atomic_fetch_xor, 480std::atomic_fetch_xor_explicit, 481std::atomic_flag, 138, 141, 457std::atomic_flag_clear, 459std::atomic_flag_clear_explicit, 460std::atomic_flag_test_and_set, 459std::atomic_flag_test_and_set_explicit, 459std::atomic_flag::clear, 459std::atomic_flag::test_and_set, 458std::atomic_init, 463std::atomic_is_lock_free, 464std::atomic_load, 465std::atomic_load_explicit, 465std::atomic_signal_fence(), 457std::atomic_store, 466std::atomic_store_explicit, 466std::atomic_thread_fence(), 456std::atomic<bool>, 143std::atomic<int>, 147std::atomic<T*>, 146std::atomic<t*>::fetch_add, 486std::atomic<t*>::fetch_sub, 487std::atomic<typ-ca�kowitoliczbowy>::fetch_add,

476std::atomic<typ-ca�kowitoliczbowy>::fetch_and,

478std::atomic<typ-ca�kowitoliczbowy>::fetch_or,

479std::atomic<typ-ca�kowitoliczbowy>::fetch_sub,

477std::atomic<typ-ca�kowitoliczbowy>::fetch_xor,

480std::atomic<unsigned long long>, 147std::atomic::compare_exchange_strong, 468std::atomic::compare_exchange_weak, 469std::atomic::exchange, 467std::atomic::is_lock_free, 464std::atomic::load, 464std::atomic::store, 466

572 Skorowidz

std::bind, 45, 333std::call_once, 86, 542std::chrono::duration, 117, 420std::chrono::duration_cast, 428std::chrono::duration::count, 423std::chrono::duration::max, 426std::chrono::duration::min, 426std::chrono::duration::zero, 425std::chrono::high_resolution_clock, 116std::chrono::steady_clock, 116, 433std::chrono::steady_clock::now, 434std::chrono::system_clock, 116, 431std::chrono::system_clock::from_time_t, 433std::chrono::system_clock::now, 432std::chrono::system_clock::to_time_t, 433std::chrono::time_point, 118, 429std::chrono::time_point::max, 431std::chrono::time_point::min, 431std::chrono::time_point::time_since_epoch, 430std::condition_variable, 95, 436std::condition_variable_any, 95, 444std::condition_variable_any::notify_all, 446std::condition_variable_any::notify_one, 446std::condition_variable_any::wait, 447std::condition_variable_any::wait_for, 448std::condition_variable_any::wait_until, 450std::condition_variable::notify_all, 437std::condition_variable::notify_one, 437std::condition_variable::wait, 344, 438std::condition_variable::wait_for, 439std::condition_variable::wait_until, 441std::copy_exception(), 112std::current_exception(), 112std::find, 306std::for_each, 51, 304std::future, 103, 106, 109, 112, 490std::future::get, 494std::future::share, 492std::future::valid, 492std::future::wait, 493std::future::wait_for, 493std::future::wait_until, 494std::kill_dependency(), 174std::lock, 72, 540std::lock_guard, 60, 528std::map<>, 207std::move(), 46, 124, 329, 333std::multimap<>, 207std::mutex, 60, 515std::mutex::lock, 516std::mutex::try_lock, 516

std::mutex::unlock, 517std::notify_all_at_thread_exit, 443std::once_flag, 86, 541std::packaged_task, 106, 391, 501std::packaged_task::get_future, 504std::packaged_task::make_ready_at_thread_exit,

506std::packaged_task::reset, 505std::packaged_task::swap, 504std::packaged_task::valid, 505std::partial_sum, 312std::partition(), 124std::promise, 109, 507std::promise::get_future, 510std::promise::set_exception, 512std::promise::set_exception_at_thread_exit, 512std::promise::set_value, 510std::promise::set_value_at_thread_exit, 511std::promise::swap, 509std::queue<>, 97std::ratio, 421, 544std::ratio_equal, 546std::ratio_greater, 548std::ratio_greater_equal, 548std::ratio_less, 547std::ratio_less_equal, 548std::ratio_not_equal, 547std::recursive_mutex, 90, 517std::recursive_mutex::lock, 519std::recursive_mutex::try_lock, 519std::recursive_mutex::unlock, 519std::recursive_timed_mutex, 524std::recursive_timed_mutex::lock, 526std::recursive_timed_mutex::try_lock, 526std::recursive_timed_mutex::try_lock_for, 526std::recursive_timed_mutex::try_lock_until, 527std::recursive_timed_mutex::unlock, 528std::ref, 45std::shared_future, 103, 113, 495std::shared_future::get, 500std::shared_future::valid, 498std::shared_future::wait, 498std::shared_future::wait_for, 499std::shared_future::wait_until, 499std::stack, 64, 66std::string, 44std::terminate(), 37, 349std::this_thread::get_id, 52, 558std::this_thread::sleep_for, 120, 559std::this_thread::sleep_until, 120, 559std::this_thread::yield, 559

Skorowidz 573

std::thread, 43, 549std::thread::detach, 557std::thread::get_id, 558std::thread::hardware_concurrency, 49, 273, 276,

280, 324, 558std::thread::id, 550std::thread::join, 557std::thread::joinable, 556std::thread::native_handle, 553std::thread::swap, 556std::timed_mutex, 520std::timed_mutex::lock, 521std::timed_mutex::try_lock, 522std::timed_mutex::try_lock_for, 522std::timed_mutex::try_lock_until, 523std::timed_mutex::unlock, 524std::try_lock, 541std::unique_lock, 79, 80, 530std::unique_lock::lock, 536std::unique_lock::mutex, 539std::unique_lock::operator, 539std::unique_lock::owns_lock, 539std::unique_lock::release, 539std::unique_lock::swap, 535, 536std::unique_lock::try_lock, 536std::unique_lock::try_lock_for, 537std::unique_lock::try_lock_until, 538std::unique_lock::unlock, 537std::unordered_map<>, 207std::unordered_multimap<>, 207store(), 140, 143, 177submit(), 326, 329, 335swap(), 64synchronizacja wspó�bie�nych operacji, 93

definicja klasy kolejki, 100funkcje otrzymuj�ce limity czasowe, 121komunikacja procesów sekwencyjnych, 126mechanizmy przysz�o�ci, 102obietnice, 109oczekiwanie na wiele w�tków, 112oczekiwanie na zdarzenie, 94operacje atomowe, 137porz�dek modyfikacji, 136pozorne budzenie, 97programowanie funkcyjne, 122projektowanie uniwersalnej kolejki, 97prosta kolejka komunikatów, 401przekazywanie zada� pomi�dzy w�tkami, 107upraszczanie kodu, 121wi�zanie zadania z przysz�o�ci�, 106

wynik oblicze� wykonywanych w tle, 103wy�cig danych, 112wywo�anie blokuj�ce z limitem czasowym, 115zapisywanie wyj�tku na potrzeby przysz�o�ci,

111zmienna warunkowa, 95

sytuacja wy�cigu, 58, 355definicja klasy stosu, 68przekazywanie referencji, 66stosowanie konstruktora, 67zwracanie wskanika, 67

szablon klasystd::atomic, 138, 140, 147, 460std::chrono::duration, 117, 420std::chrono::time_point, 118, 429std::future, 103, 109, 112, 490std::lock_guard, 60, 528std::packaged_task, 106, 391, 501std::promise, 109, 507std::ratio, 421, 544std::ratio_equal, 546std::ratio_greater, 548std::ratio_greater_equal, 548std::ratio_less, 547std::ratio_less_equal, 548std::ratio_not_equal, 547std::shared_future, 103, 113, 495std::unique_lock, 79, 80, 530TemplateDispatcher, 405

szczegó�owo�� blokady, 82szeregowanie, 185

Ttablica mieszaj�ca, 209

kube�ek, 209tablica posortowana, 209tablica wyszukiwania, 207

operacje, 208test_and_set(), 138, 141testowanie wspó�bie�nego kodu, 360

biblioteka podstawowych mechanizmów, 365czynniki dotycz�ce �rodowiska testowego, 361projektowanie struktury wielow�tkowego kodu

testowego, 366identyfikacja odr�bnych fragmentów

poszczególnych testów, 366przyk�ad testu dla struktury kolejki, 367

scenariusze testowania kolejki, 360testowanie symulowanych kombinacji, 364

wady, 365

574 Skorowidz

testowanie wspó�bie�nego kodutestowanie wydajno�ci, 369

skalowalno��, 369testy si�owe, 363

wady, 364thread_a(), 76thread_b(), 76thread_guard, 48thread_pool, 331time_since_epoch(), 118top(), 64try_lock(), 76, 80try_lock_for(), 120try_lock_until(), 120try_pop(), 98, 191, 197, 200, 202, 337try_reclaim(), 230try_steal(), 337try-catch, 40typy wywo�ywalne, 36

Uunlock(), 60, 80, 348update_data_for_widget, 44uwi�zienie, 223, 354

Vvoid(), 106

Wwait(), 96, 102, 318, 344, 348, 403wait_and_dispatch(), 405, 407wait_and_pop(), 98, 191, 202, 204wait_for(), 115, 120, 344wait_for_data(), 205wait_until(), 115warstwy abstrakcji, 30w�tki demonów, 42w�tki sprz�towe, 21wnioskowanie typów zmiennych, 122worker_thread(), 326, 335wskaniki ryzyka, 233wspó�bie�ne struktury danych, 184

aktywne oczekiwanie, 261algorytmy blokuj�ce, 220bezpiecze�stwo przetwarzania

wielow�tkowego, 184blokady wiruj�ce, 221definicja klasy kolejki, 190definicja klasy stosu, 187

drzewo binarne, 209implementacja listy z obs�ug� iteracji, 214implementacja tablicy wyszukiwania, 210jednow�tkowa implementacja kolejki, 195kolejka bez blokad, 252

uzyskiwanie nowej referencji, 259zwalnianie licznika zewn�trznego w�z�a, 260zwalnianie referencji do w�z�a, 258

kolejka nieograniczona, 206kolejka ograniczona, 206kolejka z mechanizmami blokowania

i oczekiwania, 203kolejka ze szczegó�owymi blokadami, 198kolejka ze sztucznym w�z�em, 196kolejk� z jednym producentem i jednym

konsumentem, 253lista jednokierunkowa, 194problem ABA, 266projektowanie przy u�yciu blokad, 186stos bez blokad, 227, 250struktury bez blokad, 220, 221

eliminowanie niebezpiecznych wycieków, 228kolejka, 252mechanizm odzyskiwania pami�ci, 230problem ABA, 266stos, 227uwi�zienie, 223wskazówki dotycz�ce pisania struktur, 264zalety i wady, 222zarz�dzanie pami�ci�, 228zliczanie referencji, 242zwalnianie w�z�ów, 229

struktury bez oczekiwania, 222szeregowanie, 185tablica mieszaj�ca, 209

kube�ek, 209tablica posortowana, 209tablica wyszukiwania, 207wskazówki dotycz�ce projektowania, 185wskaniki ryzyka, 233

implementacja funkcji odzyskuj�cych w�z�y, 238strategie odzyskiwania w�z�ów, 240wykrywanie w�z�ów, 233

wywo�ania blokuj�ce, 220zag�odzenie, 221zliczanie referencji, 242

wspó�bie�no��, 20bezpiecze�stwo przetwarzania, 184mechanizm przysz�o�ci, 102modele, 22nadsubskrypcja, 280oczekiwanie na zdarzenie, 94

Skorowidz 575

operacje atomowe, 136, 137podniesienia wydajno�ci, 26podzia� zagadnie�, 25projektowanie struktury danych, 184prze��czanie kontekstu, 21prze��czanie zada�, 21, 22przyk�ad programu, 32równoleg�o�� danych, 26synchronizacja operacji, 93w j�zyku C++, 28w�tki sprz�towe, 21wspó�bie�ne struktury danych, 184z wieloma procesami, 23z wieloma w�tkami, 24zarz�dzanie w�tkami, 36zmienna warunkowa, 95zrównoleglanie zada�, 26

wspó�dzielenie danych przez w�tki, 55blokowanie rekurencyjne, 90definicja klasy stosu, 68Double-Checked Locking, 85elastyczne blokowanie muteksu, 79leniwa inicjalizacja, 85lista dwukierunkowa, 56muteks, 59niezmienniki, 56ochrona danych za pomoc� muteksów, 60pami�� transakcyjna, 59problemy, 56programowanie bez blokad, 59przenoszenie w�asno�ci muteksu, 80sytuacja wy�cigu, 58szczegó�owo�� blokady, 82wy�cigu danych, 58zakleszczenie, 71

wspó�zawodnictwo, 286wybór liczby w�tków, 49

nadsubskrypcja, 51wyra�enie lambda, 37wysokie wspó�zawodnictwo, 282

zwi�kszenie prawdopodobie�stwa, 283wy�cig danych, 58, 112, 136, 355

niezdefiniowane zachowanie, 58wywo�anie blokuj�ce z limitem czasowym, 115

funkcje otrzymuj�ce limity czasowe, 121limity bezwzgl�dne, 115maksymalny czas blokowania w�tku, 115okres, 117punkt w czasie, 118wymuszanie oczekiwania na podstawie okresu,

117

zastosowanie limitu czasowego, 120zegar, 115

Zzaawansowane zarz�dzanie w�tkami, 323

przerywanie wykonywania w�tków, 340monitorowanie systemu plików w tle, 350obs�uga przerwa�, 349oczekiwanie na zmienn�, 343, 346pozosta�e wywo�ania blokuj�ce, 348przerywanie zada� wykonywanych w tle, 350punkt przerwania, 341wykrywanie przerwania, 342

pula w�tków, 324algorytm sortowania szybkiego, 330implementacja algorytmu sortowania

szybkiego, 332implementacja prostej puli w�tków, 325kolejka umo�liwiaj�ca wykradanie zada�, 336lokalne kolejki zada�, 334z mechanizmem oczekiwania na zadanie, 327z mechanizmem wykradania zada�, 337unikanie wspó�zawodnictwa w dost�pie do

kolejki, 333w�tek roboczy, 324wykradanie zada�, 335

zakleszczenie, 71, 354hierarchia blokad, 75unikanie, 71, 73

zasada jednej odpowiedzialno�ci, 277zegar, 115

czasu rzeczywistego, 116epoka zegara, 118najkrótszy mo�liwy takt, 116okres taktu, 116stabilny, 116

zegar::now(), 116zliczanie referencji, 242

licznik wewn�trzny, 243licznik zewn�trzny, 243stos bez blokad, 250umieszczanie w�z�a na stosie bez blokad, 243wykrywanie u�ywanych w�z�ów, 242zdejmowanie w�z�a ze stosu bez blokad, 245

zmienna warunkowa, 95definicja klasy kolejki, 100oczekiwanie na spe�nienie warunku, 95pozorne budzenie, 97projektowanie uniwersalnej kolejki, 97

zrównoleglanie zada�, 26

576 Skorowidz