Vlákna. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Aleš Hrabalík a Martin Hořeňovský, 2016

Podobné dokumenty
C++ 0x aka C++11. Základním kamenem je třída std::thread

Paralelní a distribuované výpočty (B4B36PDV)

Vlákna a přístup ke sdílené paměti. B4B36PDV Paralelní a distribuované výpočty

Pavel Procházka. 3. prosince 2014

Práce s knihovnami. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Martin Hořeňovský, Aleš Hrabalík, 2016

Preprocesor. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Martin Hořeňovský, Aleš Hrabalík, 2016

Přetěžování operátorů, dynamika objektů 2

Více o konstruktorech a destruktorech

Vícevláknové programování na CPU: POSIX vlákna a OpenMP I. Šimeček

PB161 Programování v jazyce C++ Přednáška 10

Vlákno odlehčený proces kód vlákna, zásobník privátní ostatní sdíleno s dalšími vlákny téhož procesu

Dynamika objektů. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze

Vector datový kontejner v C++.

Singleton obsah. Motivace Základní myšlenka Implementace Problémy. Dědičnost Obecná implementace Shrnutí. destrukce vícevláknovost

Ukazatele, dynamická alokace

Generické programování

Ošetření chyb a výjimky

Paralení programování pro vícejádrové stroje s použitím OpenMP. B4B36PDV Paralelní a distribuované výpočty

Úvod. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze. Karel Richta, Martin Hořeňovský, Aleš Hrabalík,2016

Řada programovacích jazyků nabízí prostředky pro řešení meziprocesové komunikace jako je synchronizace a řízení přístupu do kritické sekce.

PB161 Programování v jazyce C++ Přednáška 8

Domácí úkoly 2013/14

Operační systémy Tomáš Hudec. 6 Komunikace procesů (IPC) Obsah: 6.1 Klasické problémy souběhu Obědvající filosofové

Jazyk C# (seminář 5)

Martin Flusser. Faculty of Nuclear Sciences and Physical Engineering Czech Technical University in Prague. October 17, 2016

C++ přetěžování funkcí a operátorů. Jan Hnilica Počítačové modelování 19

Singleton obsah. n Motivace. n Základní myšlenka. n Implementace. n Problémy. n Dědičnost. n Obecná implementace. n Shrnutí.

C++ objektově orientovaná nadstavba programovacího jazyka C

PB161 Programování v jazyce C++ Přednáška 10

Zadání Vytvoříme jednoduchý multithread HTTP server v jazyce Java Spustíme si ho na lokálním počítači A otestujeme ho Zdrojový kód je v

PROGRAMOVÁNÍ V C++ CVIČENÍ

map, multimap - Asociativní pole v C++.

PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE DATUM VYTVOŘENÍ: KLÍČOVÁ AKTIVITA: 02 PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) HODINOVÁ DOTACE: 1

přetížení operátorů (o)

Základy C++ I. Jan Hnilica Počítačové modelování 18

Obsah. Kapitola 1 Hardware, procesory a vlákna Prohlídka útrob počítače...20 Motivace pro vícejádrové procesory...21

Standardní algoritmy vyhledávací.

Algoritmizace a programování

Příklad aplikace Klient/Server s Boss/Worker modelem (informativní)

Procesy a vlákna - synchronizace

Soubor jako posloupnost bytů

<surface name="pozadi" file="obrazky/pozadi/pozadi.png"/> ****************************************************************************

PB161 Programování v C++ Proudy pro standardní zařízení Souborové proudy Paměťové proudy Manipulátory

Vlákna. První jednoduchý program s vlákny:

Základy programování. Úloha: Eratosthenovo síto. Autor: Josef Hrabal Číslo: HRA0031 Datum: Předmět: ZAP

Pokud zadání nerozumíte nebo se vám zdá nejednoznačné, zeptejte se. Pište čitelně, nečitelná řešení nebudeme uznávat.

9. lekce Úvod do jazyka C 4. část Funkce, rekurze Editace, kompilace, spuštění Miroslav Jílek

Konstruktory a destruktory

30. Vlákna, jejich atributy, metody, organizace a stavy. Možnosti synchronizace. (A7B36PVJ)

Funkční objekty v C++.

1 Nejkratší cesta grafem

MySQLi (objektově) Příklad vytvoření instance třídy včetně parametrů pro připojení: $mysqli = new mysqli('localhost', 'login', 'heslo', 'databaze');

Paralelní programování

Doporučené postupy. Karel Richta a kol. katedra počítačů FEL ČVUT v Praze

IRAE 07/08 Přednáška č. 2. atr1 atr2. atr1 atr2 -33

ZOS 9. cvičení, ukázky kódu. Pavel Bžoch

Jazyk C++ I. Šablony 2

8 Třídy, objekty, metody, předávání argumentů metod

Management procesu I Mgr. Josef Horálek

Abstraktní datové typy

Vyhledávání. doc. Mgr. Jiří Dvorský, Ph.D. Katedra informatiky Fakulta elektrotechniky a informatiky VŠB TU Ostrava. Prezentace ke dni 21.

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

přetížení operátorů (o)

Procesy a vlákna (Processes and Threads)

PB071 Programování v jazyce C

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

Mělká a hluboká kopie

Principy operačních systémů. Lekce 6: Synchronizace procesů

Principy operačních systémů. Lekce 5: Multiprogramming a multitasking, vlákna

Od CGI k FastCGI. Uvedené dílo podléhá licenci Creative Commons Uved te autora 3.0 Česko.

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

PB161 Programování v jazyce C++ Přednáška 4

PB161 Programování v jazyce C++ Přednáška 9

Programování v C++ 3, 3. cvičení

Zapouzdření. Tomáš Pitner, upravil Marek Šabo

Ukázka zkouškové písemka OSY

Výjimky a ošetřování chyb v PHP. Who is General Failure and why is he reading my disk?!

Jazyk C# (seminář 6)

Pokud zadání nerozumíte nebo se vám zdá nejednoznačné, zeptejte se. Pište čitelně, nečitelná řešení nebudeme uznávat.

PB přednáška (23. listopadu 2015)

Základy objektové orientace I. Únor 2010

Programování II. Návrh programu I 2018/19

Statické proměnné a metody. Tomáš Pitner, upravil Marek Šabo

Programování v C++, 2. cvičení

Paralelní a distribuované výpočty (B4B36PDV)

Šablony, kontejnery a iterátory

Hrátky s funkcemi. PV173 Programování v C++11. Vladimír Štill, Jiří Weiser. Fakulta Informatiky, Masarykova Univerzita. 29.

NPRG051 Pokročilé programování v C /17 Úkol 2

Pokročilé programování v jazyce C pro chemiky (C3220) Statické proměnné a metody, šablony v C++

PŘETĚŽOVÁNÍ OPERÁTORŮ

Cvičení 9 - Monitory. monitor m; var proměnné... procedure p; begin... end; begin inicializace; end;

Zápis programu v jazyce C#

Paralelní programování

Pokročilé programování v jazyce C pro chemiky (C3220) Třídy v C++

Mnohotvarost (polymorfizmus)

Paralelní programování

Množina v C++ (set, multiset).

Programování se šablonami

PB161 Programování v jazyce C++ Přednáška 7

Transkript:

Vlákna Karel Richta a kol. katedra počítačů FEL ČVUT v Praze Karel Richta, Aleš Hrabalík a Martin Hořeňovský, 2016 Programování v C++, A7B36PJC 09/2015, Lekce 12 https://cw.fel.cvut.cz/wiki/courses/a7b36pjc/start

Motivace Všichni jsme už někdy potkali zamrzlé GUI. Obvykle se to děje kvůli čekání na dokončení práce nebo čtení z disku. Děje se to proto, že počítač dělá věci vždy jednu po druhé... ačkoliv by se během prodlev hodilo dělat něco jiného, trpělivě vyčkává. Vlákna umožňují dělat více věcí zároveň. Když tedy chceme, aby aplikace zároveň mohla pracovat i reagovat na uživatele, použijeme dvě nebo více vláken. Vlákna se též dají použít k urychlení běhu programu.

Vlákna Běh programu se nazývá proces. Proces obsahuje jedno nebo více vláken. Proces 1 Vlákno na GUI Vlákno na práci Proces 2 Vlákno na TCP/IP Vlákno na checksum

Vlákna a paměť Procesy žijí v navzájem oddělených adresních prostorech. Vlákna jednoho procesu naopak paměť sdílejí. Proces 1 Proces 2 Vlákno na GUI Vlákno na práci paměť Vlákno na TCP/IP Vlákno na checksum paměť

Podpora vláken ve standardním C++ C++ standardní knihovna poskytuje pro práci s vlákny různé prostředky. Vlákna std::thread Mutexy std::mutex, std::shared_mutex a další RAII zámky std::unique_lock a další std::future, std::promise, std::async std::condition_variable Atomické proměnné std::atomic<t> Dále C++ definuje tzv. memory model, model chování vícevláknové aplikace. Tím se nebudeme příliš zabývat.

Vlákna v C++ (<thread>) Vlákna jsou exportována hlavičkou <thread>. Konstruktor vlákna získá prvním argumentem funkci, kterou bude vykonávat, ostatní argumenty předá volané funkci. Vlákno je nejhrubší jednotka paralelismu. #include <iostream> #include <thread> #include <chrono> using namespace std::chrono_literals; void function(int id, int n) { for (int i = 0; i < n; ++i) { std::cout << "vlakno " << id << " rika ahoj\n"; std::this_thread::sleep_for(10ms); int main() { std::thread t1(function, 1, 2); std::thread t2(function, 2, 4); t1.join(); t2.join(); vlakno vlakno 2 rika ahoj 1 rika ahoj vlakno 1 rika ahoj vlakno 2 rika ahoj vlakno 2 rika ahoj vlakno 2 rika ahoj

Vlákna v C++ (<thread>) Dříve, než je zavolán destruktor std::thread, musíme zavolat metodu join spouštějící vlákno čeká, než spuštěné vlákno doběhne, nebo zavolat metodu detach spuštěné vlákno je autonomní. #include <iostream> #include <thread> #include <chrono> using namespace std::chrono_literals; void function(int id, int n) { for (int i = 0; i < n; ++i) { std::cout << "vlakno " << id << " rika ahoj\n"; std::this_thread::sleep_for(10ms); int main() { std::thread t1(function, 1, 2); std::thread t2(function, 2, 4); t1.join(); // hlavní vlákno čeká na ukončení vlákna t1 t2.join(); // hlavní vlákno čeká na ukončení vlákna t2

Vlákna v C++ (<thread>) Dříve, než je zavolán destruktor std::thread, musíme zavolat metodu join spouštějící vlákno čeká, než spuštěné vlákno doběhne, nebo zavolat metodu detach spuštěné vlákno je autonomní. #include <iostream> #include <thread> #include <chrono> using namespace std::chrono_literals; void function(int id, int n) { for (int i = 0; i < n; ++i) { std::cout << "vlakno " << id << " rika ahoj\n"; std::this_thread::sleep_for(10ms); když zapomeneme na join nebo detach... int main() { std::thread t1(function, 1, 2); std::thread t2(function, 2, 4); t1.join(); // hlavní vlákno čeká na ukončení vlákna t1 t2.join(); // hlavní vlákno čeká na ukončení vlákna t2...proces se okamžitě ukončí.

Synchronizace Vzhledem k tomu, že vlákna sdílejí paměť, je potřeba je synchronizovat. Jinak se budou dít divné věci! #include <iostream> #include <thread> int main() { int counter = 0; auto thread_func = [&counter]() { for (int i = 0; i < 1'000'000; ++i) { counter++; counter--; ; >>> 0 >>> 1 >>> -3053 >>> 4 >>> -2... auto t1 = std::thread(thread_func); auto t2 = std::thread(thread_func); t1.join(); t2.join(); std::cout << counter << std::endl;

Mutexy a zámky (<mutex>) Mutex je struktura pro synchronizaci vláken. Vlákna žádají o vlastnictví (uzamčení) mutexu. Mutex může být v danou chvíli uzamčen pouze jedním vláknem. Zámky jsou RAII třídy, které obsluhují uzamykání a odemykání mutexů. Mutexy a zámky jsou exportovány hlavičkou <mutex>.... int counter = 0; std::mutex mutex; auto thread_func = [&counter, &mutex]() { for (int i = 0; i < 1'000'000; ++i) { std::unique_lock<std::mutex> lock(mutex); counter++; counter--; // destruktor lock odemkne mutex ;... >>> 0 >>> 0 >>> 0 >>> 0 >>> 0...

Mutexy a zámky problémy Používání zámků s sebou nese různé potenciální problémy. Jeden z nich je deadlock. Deadlock je stav ve kterém program nemůže pokračovat, protože se různá vlákna navzájem blokují. int Pokud main() { spustíme tento kód, výpisy přestanou okamžitě. std::mutex m1; Proč? std::mutex m2; auto thread_func = [](std::mutex& first_mutex, std::mutex& second_mutex, int id) { while (true) { std::lock_guard<std::mutex> l1(first_mutex); std::cout << "Vlakno " << id << " rika ahoj.\n"; std::lock_guard<std::mutex> l2(second_mutex); ; std::thread t1(thread_func, std::ref(m1), std::ref(m2), 0); std::thread t2(thread_func, std::ref(m2), std::ref(m1), 1); t1.join(); t2.join();

Mutexy a zámky problémy K deadlocku obvykle dojde, pokud se více vláken pokusí zamknout stejné mutexy v různém pořadí. Pokud spustíme tento kód, výpisy přestanou okamžitě. Proč? int main() { std::mutex m1; std::mutex m2; auto thread_func = [](std::mutex& first_mutex, std::mutex& second_mutex, int id) { while (true) { std::lock_guard<std::mutex> l1(first_mutex); std::cout << "Vlakno " << id << " rika ahoj.\n"; std::lock_guard<std::mutex> l2(second_mutex); ; std::thread t1(thread_func, std::ref(m1), std::ref(m2), 0); std::thread t2(thread_func, std::ref(m2), std::ref(m1), 1); t1.join(); t2.join();

Mutexy a zámky problémy vlákno 1: vlákno 1: vlákno 2: vlákno 2: vlákno 2: vlákno 1:... std::lock_guard<std::mutex> l1(first_mutex); // vlákno 1 zamkne m1 std::cout << "Vlakno " << id << " rika ahoj.\n"; std::lock_guard<std::mutex> l1(first_mutex); // vlákno 2 zamkne m2 std::cout << "Vlakno " << id << " rika ahoj.\n"; std::lock_guard<std::mutex> l2(second_mutex); // vlákno 2 čeká, chce zamknout m1 std::lock_guard<std::mutex> l2(second_mutex); // vlákno 1 čeká, chce zamknout m2 výsledek: deadlock int main() { std::mutex m1; std::mutex m2; auto thread_func = [](std::mutex& first_mutex, std::mutex& second_mutex, int id) { while (true) { std::lock_guard<std::mutex> l1(first_mutex); std::cout << "Vlakno " << id << " rika ahoj.\n"; std::lock_guard<std::mutex> l2(second_mutex); ; std::thread t1(thread_func, std::ref(m1), std::ref(m2), 0); std::thread t2(thread_func, std::ref(m2), std::ref(m1), 1); t1.join(); t2.join();

Mutexy a zámky problémy K deadlocku nemůže dojít, použijeme-li std::lock. std::lock uzamkne všechny mutexy, které mu poskytneme. Mutexy pak musíme umístit do std::lock_guard s druhým parametrem std::adopt_lock, jinak se mutexy neodemknou. int main() { std::mutex m1; std::mutex m2; auto thread_func = [](std::mutex& first_mutex, std::mutex& second_mutex, int id) { while (true) { std::lock(first_mutex, second_mutex); std::lock_guard<std::mutex> l1(first_mutex, std::adopt_lock); std::cout << "Vlakno " << id << " rika ahoj.\n"; std::lock_guard<std::mutex> l2(second_mutex, std::adopt_lock); ; std::thread t1(thread_func, std::ref(m1), std::ref(m2), 0); std::thread t2(thread_func, std::ref(m2), std::ref(m1), 1); t1.join(); t2.join();

Future, Promise a Async v C++ (<future>) Protože vlákna neumí vracet hodnotu, existují tzv. futures. S futures jsou spojeny tzv. přísliby (promise) a async. Future a promise reprezentují budoucí výsledek. Future umožňuje jeho čtení. Promise jeho zápis. std::vector<int> numbers; // ziskame cisla // pripravime si future a promise std::promise<int> sum_promise; std::future<int> sum_future = sum_promise.get_future(); // Nechame jine vlakno, aby je secetlo std::thread([](std::promise<int> promise, std::vector<int> numbers) { promise.set_value(std::accumulate(begin(numbers), end(numbers), 0));, std::move(sum_promise), std::move(numbers)).detach(); // zatimco delame jinou praci int sum = sum_future.get(); // ziskame vysledek, pokud jeste neni, blokujeme

Future, Promise a Async v C++ (<future>) Pro ulehčení práce se std::promise a std::future existuje ještě std::async. std::async bere při konstrukci způsob vykonání, funkci, kterou má vykonat, a argumenty, které předá funkci. std::async vrátí std::future a následně nějak zařídí, aby se funkce vykonala. template <typename RandomAccessIter> int parallel_mult(randomaccessiter beg, RandomAccessIter end) { auto dist = std::distance(beg, end); if (dist <= 1000) { return std::accumulate(beg, end, 1, std::multiplies<>{); RandomAccessIter middle = beg + dist / 2; auto rmul = std::async(std::launch::async, parallel_mult<randomaccessiter>, middle, end); int lmul = parallel_mult(beg, middle); return lmul * rmul.get();

Future, Promise a Async v C++ (<future>) POZOR: Destruktor std::future vytvořené přes std::async je blokující a čeká na ukončení vykonávání funkce uvnitř std::async. std::async(std::launch::async, foo); // vytvoří dočasnou future a čeká na jejím destruktoru std::async(std::launch::async, bar); // funkce bar nemůže začít, dokud neskončí funkce foo Je potřeba uložit future do dočasné proměnné, nebo jiným způsobem prodloužit její život. { auto t1 = std::async(std::launch::async, foo); // začne probíhat foo auto t2 = std::async(std::launch::async, bar); // začne probíhat bar bez ohledu na foo // zde se spustí destruktor t2 a t1, čeká se na ukončení foo i bar. std::future<void> qux() { return std::async(std::launch::async, foo); // začne probíhat foo // nedojde k blokování, protože je future vrácena z funkce ven

Condition Variables (<condition_variable>) Podmínkové proměnné slouží ke komunikaci mezi vlákny. Na rozdíl od mutexů, které slouží k synchronizaci. Podmínkové proměnné jsou vždy spojené s nějakým mutexem. Na podmínkových proměnných mohou vlákna čekat na signál od jiného vlákna. S čekáním na podmínkové proměnné vždy souvisí predikát, který kontroluje, jestli se vlákno mělo probudit vlákno se mohlo probudit i samovolně, nebo nemusí být schopno v současném stavu systému pokračovat.

Condition Variables (<condition_variable>) template <typename T> class synchronized_box { T element; std::mutex mutex; std::condition_variable cv; bool is_ready, closed; public: synchronized_box():is_ready(false),closed(false) { void insert(const T& e) { std::unique_lock<std::mutex> lock(mutex); cv.wait(lock, [&]() { return!is_ready closed; ); if (closed) { return; element = e; is_ready = true; cv.notify_one(); bool take_out(t& out) { std::unique_lock<std::mutex> lock(mutex); cv.wait(lock, [&]() { return is_ready closed; ); if (closed) { return false; out = std::move(element); is_ready = false; cv.notify_one(); return true;... ;... void close() { std::unique_lock<std::mutex> lock(mutex); closed = true; cv.notify_all(); ~synchronized_box(){ close(); int main() { synchronized_box<int> box; auto thread_func = [&]() { int temp; box.take_out(temp); std::cout << temp << '\n'; ; std::thread t(thread_func); std::this_thread::sleep_for(10ms); box.insert(1); t.join();

Atomické proměnné (<atomic>) Atomické proměnné jsou alternativa k zámku nad jednou proměnnou. Zamknutí mutexu je poměrně nákladná operace. Atomické operace nejsou zdarma, ale jsou levnější. Pracuje se s nimi podobně jako s neatomickými proměnnými, ale operace nad nimi jsou nedělitelné. int main() { std::atomic<int> counter = 0; auto thread_func = [&counter]() { for (int i = 0; i < 1'000'000; ++i) { counter++; counter--; ; >>> 0 >>> 0 >>> 0 >>> 0 >>> 0... auto t1 = std::thread(thread_func); auto t2 = std::thread(thread_func); t1.join(); t2.join(); std::cout << counter << std::endl;

Atomické proměnné (<atomic>) Atomické operace mohou mít různou sílu. Můžeme ji určit pomocí druhého parametru, který poskytneme dané atomické operaci. Jednotlivé stupně mění, jaké optimalizace smí kompilátor provést. Výchozí hodnota požaduje, aby byla dodržena sekvenční konzistence. To je rozumná varianta, ačkoliv může být pomalá. int main() { std::atomic<int> counter = 0; auto thread_func = [&counter]() { for (int i = 0; i < 1'000'000; ++i) { counter.fetch_add(1, memory_order_seq_cst); counter.fetch_sub(1, memory_order_seq_cst); ; >>> 0 >>> 0 >>> 0 >>> 0 >>> 0... auto t1 = std::thread(thread_func); auto t2 = std::thread(thread_func); t1.join(); t2.join(); std::cout << counter << std::endl;

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0????

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0??? x = 1; y = 1;

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; x = 1; y = 1; r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0 x = 1; y = 1;??

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; x = 1; y = 1; r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0 x = 1; y = 1; y = 1; x = 1;?

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; x = 1; y = 1;???????????????????????????? r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0 x = 1; y = 1; y = 1; x = 1;

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; x = 1; y = 1;!!!!!!!!!!!!!!!!!!!!!!!!!!!! r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0 x = 1; y = 1; y = 1; x = 1;!

Proč chceme sekvenční konzistenci Mějme kousek pseudokódu: Jaké různé výsledky byste čekali? x = 1; x = 0; y = 0; Vlákno 1 Vlákno 2 y = 1; x = 1; y = 1;!!!!!!!!!!!!!!!!!!!!!!!!!!!! r1 = 1 r2 = 1 r1 = 0 r2 = 1 r1 = 1 r2 = 0 r1 = 0 r2 = 0 x = 1; y = 1; y = 1; x = 1; Poslední případ je tzv. relaxovaný. Tomuto výsledku sekvenční konzistence zabrání.!

Synchronizace nad více proměnnými Pozor, pro synchronizaci nad více proměnnými je stále potřeba zámek. Uvažte tuto funkci pro převod peněz. Pokud se pokusíme provést více plateb zároveň, nemůže se nám stát, že se nějaký účet dostane do mínusu? bool transaction(std::atomic<int>& balance1, std::atomic<int>& balance2, int amount) { if (balance1 >= amount) { balance1 -= amount; balance2 += amount; return true; return false;

Synchronizace nad více proměnnými Pozor, pro synchronizaci nad více proměnnými je stále potřeba zámek. Uvažte tuto funkci pro převod peněz. Pokud se pokusíme provést více plateb zároveň, nemůže se nám stát, že se nějaký účet dostane do mínusu? bool transaction(std::atomic<int>& balance1, std::atomic<int>& balance2, int amount) { if (balance1 >= amount) { balance1 -= amount; balance2 += amount; return true; return false; vlákno 1: vlákno 2: vlákno 1: vlákno 1: vlákno 2: vlákno 2: vlákno 2: vlákno 1:... if (balance1 >= amount) { if (balance1 >= amount) { balance1 -= amount; balance2 += amount; balance1 -= amount; balance2 += amount; return true; return true;...

Synchronizace nad více proměnnými Pro synchronizaci více proměnných je stále potřeba použít zamykání. bool transaction(account& account1, account& account2, int amount) { std::lock(account1.mutex, account2.mutex); // Zamkneme pristup k oboum uctum // Prevedeme vlastnictvi do lock guardu std::lock_guard<std::mutex> lg1(account1.mutex, std::adopt_lock); std::lock_guard<std::mutex> lg2(account2.mutex, std::adopt_lock); // Ted muzeme prevest penize aniz by hrozilo, ze se dostaneme do zaporu if (account1.balance >= amount) { account1.balance -= amount; account2.balance += amount; return true; struct account { std::mutex mutex; return false; std::atomic<int> balance; ;

Běžné modely paralelismu Existuje mnoho běžných modelů paralelismu v programech. My si probereme 3 základní. Boss-Worker Hlavní vlákno rozděluje úkoly ostatním vláknům Pipeline Každé vlákno provede nějakou práci nad daty a pošle výsledek dalšímu. Work crew Více vláken vykonává stejnou práci nad různými daty.

Work crew model Tento model je používaný například v OpenMP. Vlákna uvnitř jedné skupiny sdílejí kód, ale nesdílejí data. void process_data(std::vector<foobar>& data) { auto thread_func = [&](int from, int to) { for (int i = from; i < to; ++i) { process_foobar(data[i]); ; int num_threads = std::thread::hardware_concurrency(); int part_size = data.size() / num_threads; int mod = data.size() % num_threads; std::vector<std::thread> threads; for (int i = 0; i < num_threads; ++i) { threads.emplace_back(thread_func, i * part_size + std::min(i, mod), (i + 1) * part_size + std::min(i+1, mod)); for (auto& t : threads) { t.join();

Pipeline model Zřetězení po sobě následujících úkolů, jako pipe v UNIXu: find. grep "homework" grep "PJC" Každá část vykoná jednoduchý úkol s daty a pošle výsledek dál. void pipeline(const std::vector<spam>& data) { std::vector<std::thread> threads; synchronized_box<spam> pipe_start; synchronized_box<ham> pipe_mid; synchronized_box<lunch> pipe_end; threads.emplace_back([&]() { spam temp; while (pipe_start.take_out(temp)) { pipe_mid.insert(process_spam(temp)); pipe_mid.close(); ); threads.emplace_back([&]() { ham temp; while (pipe_mid.take_out(temp)) { pipe_end.insert(process_ham(temp)); pipe_end.close(); ); threads.emplace_back([&]() { lunch lunch; while(pipe_end.take_out(lunch)){ eat(lunch); ); for (auto& d : data) { pipe_start.insert(d); pipe_start.close(); for (auto& t : threads) { t.join();

Boss-Worker model Jedno vlákno je privilegované a rozdává práci. Ostatní vlákna zpracovávají práci a vrací výsledky. Typicky používané třeba pro uživatelské rozhraní aplikace. Boss vlákno přijímá uživatelovy požadavky a dává je jiným vláknům k vykonání. Tím se zamezí zaseknutí aplikace během provádění práce. Též se často používá pro obsluhu HTTP dotazů. Nadřízený přijímá požadavky a dává je podřízeným, aby je vyřídili.

Boss-Worker model (příklad) class worker { worker(synchronized_queue<request>& q):queue(q) {... //... std::thread thread; synchronized_queue<request>& queue; ; int main() { // Prepare workers synchronized_queue<request> work_queue; std::vector<worker> workers; for (int w = 0; w < std::thread::hardware_concurrency(); ++w) { workers.push_back(worker(work_queue)); // Open socket socket s("127:0:0:1:80"); // Keep adding work while (true) { auto request = read_request(s); work_queue.insert(request);

Děkuji za pozornost. 37