I IB109 Návrh a implementace paralelních systémů Pokročilá rozhraní pro implementaci paralelních aplikací Jiří Barnat Jiný způsob programování v prostředí se sdílenou pamětí Nevýhody POSIX Threads a Lock-free přístupu 9 Na příliš nízké úrovni • Vhodné pro systémové programátory • „Příliš složitý přístup na řešení jednoduchých věcí." Co bychom chtěli o Paralelní konstrukce na úrovni programovacího jazyka • Prostředek vhodný pro aplikační programátory • Snadné vyjádření běžně používaných paralelních konstrukcí IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 2/54 OpenMP IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 3/54 Myšlenka OpenMP Myšlenka • Programátor specifikuje co chce, nějak se to má udělat. • Náznak deklarativního přístupu v imperativním programování. Realizace o Programátor informuje překladač o zamýšlené paralelizaci uvedením značek ve zdrojovém kódu a označením bloků. • Při překladu překladač sám doplní nízkoúrovňovou realizaci paralelizace. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 4/54 Styl programování s OpenMP OpenMP nabízí o Pragma direktivy překladače #pragma omp direktiva [seznam klauzulí] • Knihovní funkce • Proměnné prostředí Překlad kódu • Překladač podporující standard OpenMP • při překladu pomocí GCC je nutná volba -fopenmp • g++ -fopenmp myapp.c • Podporováno nejpoužívanějšími překladači (i Visual C++) • Možno přeložit do sekvenčního kódu WWW http://www.openmp.org IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 5/54 Direktiva parallel - příklad v C++ 1 #include 2 main () 3 { 4 int nthreads, tid; 5 #pragma omp parallel private(tid) 6 { 7 tid = omp_get_thread_num() ; 8 printf ("Hello World from thread = °/.d\n", tid); 9 if (tid == 0) 10 { 11 nthreads = omp_get_num_threads(); 12 printf("Number of threads = %d\n", nthreads); 13 } 14 } 15 } IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 6/54 Direktiva parallel Použití o Strukturovaný blok, tj. {...}, následující za touto direktivou se provede paralelně. 9 Mimo paralelní bloky se kód vykonává sekvenčně. • Vlákno, které narazí na tuto direktivu se stává hlavním vláknem (master) a má identifikaci vlákna rovnou 0. Podmíněné spuštění • Klauzule: if (výraz typu bool) • Vyhodnotí-li se výraz na falše direktiva parallel se ignoruje a následující blok je proveden pouze v jedné kopii. Stupeň paralelismu • Počet vláken. o Přednastavený počet specifikován proměnnou prostředí. • Klauzule: num_threads (výraz typu int) IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 7/54 Direktiva parallel - datová lokalita Klauzule: priváte (seznam proměnných) • Vyjmenované proměnné se zduplikují a stanou se lokální proměnné v každém vlákně. Klauzule: firstprivate (seznam proměnných) • Viz priváte s tím, že všechny kopie proměnných jsou inicializované hodnotou originální kopie. Klauzule: shared (seznam proměnných) o Vyjmenované proměnné budou explicitně existovat pouze v jedné kopii. • Přístup ke sdíleným proměnným nutno serializovat. Klauzule: default ([shared|none]) 9 shared: všechny proměnné jsou sdílené, pokud není uvedeno jinak. • none: vynucuje explicitní uvedení každé proměnné v klauzuli priváte nebo v klauzuli shared. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 8/54 Direktiva parallel - redukce Klauzule: reduction (operátor: seznam proměnných) • Při ukončení paralelního bloku jsou vyjmenované privátní proměnné zkombinovaný pomocí uvedeného operátoru. 9 Kopie uvedených proměnných, které jsou platné po ukončení paralelního bloku, jsou naplněny výslednou hodnotou. • Proměnné musejí být skalárního typu (nesmí být pole, struktury, atp.). Použitelné operátory: +, *, -, &, , " , && a IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 9/54 Direktiva for Použití • Iterace následujícího for-cyklu budou provedeny paralelně 9 Musí být použito v rámci bloku za direktivou parallel (jinak proběhne sekvenčně). o Možný zkrácený zápis: #pragma omp parallel for Klauzule: private, firstprivate, reduction • Stejné jako pro direktivu parallel. Klauzule: lastprivate o Hodnota privátní proměnné ve vláknu zpracovávající poslední iteraci for cyklu je uložena do kopie proměnné platné po skončení cyklu. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 10/54 Direktiva f or Klauzule: ordered Bloky označené direktivou ordered v těle paralelně prováděného cyklu jsou provedeny v tom pořadí, v jakém by byly provedeny sekvenčním programem. • Klauzule ordered je povinná, pokud tělo cyklu obsahuje ordered bloky. Klauzule: nowait • Jednotlivá vlákna se nesynchronizují po provedení cyklu. Klauzule: schedule (typ plánování [, velikost]) a Určuje jak budou iterace rozděleny/mapovány mezi vlákna. • Implicitní plánování je závislé na implementaci. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 11/54 Direktiva for - Plánování iterací static o Iterace cyklu rozděleny do bloků o specifikované velikosti. • Bloky staticky namapovány na vlákna (round-robin). • Pokud není uvedena velikost, iterace rozděleny mezi vlákna rovnoměrně (pokud je to možné). dynamic 9 Bloky iterací cyklu v počtu specifikovaném parametrem velikost přidělovány vláknům na žádost, tj. v okamžiku, kdy vlákno dokončilo předchozí práci. • Výchozí velikost bloku je 1. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 12/54 Direktiva f or - Plánovaní iterací guided • Bloky iterací mají velikost proporcionální k počtu nezpracovaných iterací poděleným počtem vláken. • Specifikována velikost k, udává minimální velikost bloku (výchozí hodnota 1). • Příklad: • k = 7, 200 volných iterací, 8 vláken • Velikosti bloků: 200/8=25, 175/8=21, ... , 63/8 = 7, ... runtime 9 Typ plánování určen až za běhu proměnnou OMP.SCHEDULE. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 13/54 Direktiva sections Použití 9 Strukturované bloky, každý označený direktivou section, mohou být v rámci bloku označeným direktivou sections provedeny paralelně. a Možný zkrácený zápis #pragma omp parallel sections • Umožňuje definovat různý kód pro různá vlákna. Klauzule: priváte, f irstprivate, reduction, nowait • Stejné jako v předchozích případech Klauzule: lastprivate • Hodnoty privátních proměnných v poslední sekci (dle zápisu kódu) budou platné po skončení bloku sections. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 14/54 1 #include 2 main () 3 { 4 #pragma omp parallel sections 5 { 6 #pragma omp section 7 { 8 printf("Thread A."); 9 } 10 #pragma omp section 11 { 12 printf("Thread B."); 13 } 14 } 15 } IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 15/54 Vnořování direktiv parallel Nevnořený paralelismus • Direktiva parallel určuje vznik oblasti paralelního provádění. • Direktivy for a sections určují jak bude práce mapována na vlákna vzniklé dle rodičovské direktivy parallel. Vnořený paralelismus • Při nutnosti paralelismu v rámci paralelního bloku, je třeba znovu uvést direktivu parallel. • Vnořování je podmíněné nastavením proměnné prostředí OMP_NESTED (hodnoty TRUE, FALŠE). • Typické použití: vnořené for-cykly o Obecně je vnořování direktiv v OpenMP poměrně komplikované, nad rámec tohoto tutoriálu. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 16/54 Direktiva barrier Bariéra • Místo, které je dovoleno překročit, až když k němu dorazí všechna ostatní vlákna. • Direktiva bez klauzulí, tj. #pragma omp barrier. • Vztahuje se ke strukturálně nejbližší direktivě parallel. • Musí být voláno všemi vlákny v odpovídajícím bloku direktivy parallel. Poznámka ke kódování • Direktivy překladače nejsou součástí jazyka. • Je možné, že v rámci překladu bude vyhodnocen blok, ve kterém je umístěna direktiva bariéry, jako neproveditelný blok a odpovídající kód nebude ve výsledném spustitelném souboru vůbec přítomen. a Direktivu barrier, je nutné umístit v bloku, který se bezpodmínečně provede (zodpovědnost programátora). IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 17/54 Direktiva single a master Direktiva single • V kontextu paralelně prováděného bloku je následující strukturní blok proveden pouze jedním vláknem, přičemž není určeno kterým. Klauzule: priváte, f irstprivate Klauzule: nowait • Pokud není uvedena, tak na konci strukturního bloku označeného direktivou single je provedena bariéra. Direktiva master 9 Speciální případ direktivy single. • Tím vláknem, které provede strážený blok, bude hlavní (master) vlákno. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 18/54 Direktiva critical a atomic Direktiva critical 9 Následující strukturovaný blok je chápán jako kritická sekce a může být prováděn maximálně jedním vláknem v daném čase. • Kritická sekce může být pojmenována, souběžně je možné provádět kód v kritických sekcích s jiným názvem. • Pokud není uvedeno jinak, použije se implicitní jméno, o #pragma omp critical [(name)] Direktiva atomic • Nahrazuje kritickou sekci nad jednoduchými modifikacemi (updaty) proměnných v paměti. o Atomicita se aplikuje na jeden následující výraz. 9 Obecně výraz musí být jednoduchý (jeden load a store). • Neatomizovatelný výraz: x = y = 0; IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 19/54 Direktiva flush Problém (nestálé proměnné) 9 Modifikace sdílených proměnných v jednom vlákně může zůstat skryta ostatním vláknům. Řešení • Explicitní direktiva pro kopírování hodnoty proměnné z registru do paměti a zpět. • #pragma omp flush [(seznam)] Použití • Po zápisu do sdílené proměnné. • Před čtením obsahu sdílené proměnné. • Implicitní v místech bariéry a konce bloků (pokud nejsou bloky v režimu nowait). IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 20/54 Direktiva threadprivate a copyin Problém (thread-private data) o Při statickém mapování na vlákna je drahé při opakovaném vzniku a zániku vláken vytvářet kopie privátních proměnných. • Občas chceme privátní globální proměnné. Řešení • Perzistentní privátní proměnné (přetrvají zánik vlákna), o Při z nov u vytvoře ní vlákna, se proměnné znovupoužijí. • #pragma omp threadprivate (seznam) Omezení • Nesmí se použít dynamické plánování vláken. o Počet vláken v paralelních blocích musí být shodný. Direktiva copyin • Jako threadprivate, ale s inicializací. o Viz priváte versus f irstprivate. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 21/54 OpenMP knihovní funkce - Počet vláken void omp_set_num_threads (int num.threads) • Specifikuje kolik vláken se vytvoří při příštím použití direktivy parallel. • Musí být použito před samotnou konstrukcí parallel. • Je přebito klauzulí num_threads, pokud je přítomna. o Musí být povoleno dynamické modifikování procesů (OMP_DYNAMIC, omp_set_dynamic()). int omp_get_num_threads () • Vrací počet vláken v týmu strukturálně nejbližší direktivy parallel, pokud neexistuje, vrací 1. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 22/54 OpenMP knihovní funkce - Počet vláken a procesorů int omp_get_max_threads () • Vrací maximální počet vláken v týmu. int omp_get_thread_num () • Vrací unikátní identifikátor vlákna v rámci týmu. int omp_get_num_procs () • Vrací počet dostupných procesorů, které mohou v daném okamžiku participovat na vykonávání paralelního kódu. int omp_in_parallel () • Vrací nenula pokud je voláno v rozsahu paralelního bloku. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 23/54 OpenMP knihovní funkce- ontrola vytváření vláken void omp_set_dynamic (int dynamic_threads) int omp_get_dynamic() o Nastavuje a vrací, zda je programátorovi umožněno dynamicky měnit počet vláken vytvořených při dosažení direktivy parallel. Nenulová hodnota dynamic_threads značí povoleno. void omp_set_nested (int nested) int omp_get_dynamic() 9 Nastavuje a vrací, zda je povolen vnořený paralelismus. 9 Pokud není povoleno, vnořené paralelní bloky jsou serializovány. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 24/54 OpenMP knihovní funkce - Mutexy void omp_init_lock (omp_lock_t *lock) void omp_destroy_lock (omp_lock_t *lock) void omp_set_lock (omp_lock_t *lock) void omp_unset_lock (omp_lock_t *lock) int omp_test_lock (omp_lock_t *lock) void omp_init_nest_lock (omp_nest_lock_t *lock) void omp_destroy_nest_lock (omp_nest_lock_t *lock) void omp_set_nest_lock (omp_nest_lock_t *lock) void omp_unset_nest_lock (omp_nest_lock_t *lock) int omp_test_nest_lock (omp_nest_lock_t *lock) • Inicializuje, ničí, blokujícně čeká, odemyká a testuje - • - normální a rekurzivní mutex. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 25/54 Proměnné prostředí OMP_NUM_THREADS • Specifikuje defaultní počet vláken, který se vytvoří při použití direktivy parallel. OMP_DYNAMIC • Hodnota TRUE, umožňuje za běhu měnit dynamicky počet vláken. OMP_NESTED • Povoluje hodnotou TRUE vnořený paralelismus. • Hodnotou FALŠE specifikuje, že vnořené paralelní konstrukce budou serializovány. OMP .SCHEDULE 9 Udává defaultní nastavení mapování iterací cyklu na vlákna. o Příklady hodnot: "static,4", dynamic, guided. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 26/54 Intel's Thread Building Blocks (TBB) IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 27/54 Thread Building Blocks Co je Intel TBB • TBB je C++ knihovna pro vytváření vícevláknových aplikací. • Založená na principu zvaném Generic Programming. • Vyvinuto synergickým spojením Pragma direktiv (OpenMP), standardní knihovny šablon (STL, STAPL) a programovacích jazyků podporující práci s vlákny (Threaded-C, Cilk). Generic Programming • Vytváření aplikací specializací existujících předpřipravených obecných konstrukcí, objektů a algoritmů. o Lze nalézt v objektově orientovaných jazycích (C++, JAVA). o V C++ jsou obecnou konstrukcí šablony (templates). • Queue Queue& range ) const { for( int i=range.begin(); i!=range.end(); ++i ) output[i] = (input[i-l]+input[i]+input[i+1])*(l/3.f); } }; Average avg; parallel_for ( blocked_range ( 1, n ), avg ); IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 32/54 Koncept dělení • Instance některých tříd je nutné za běhu (rekurzivně) dělit, o Zavádí se nový typ konstruktoru, dělicí konstruktor: X::X(X& x, split) • Dělicí konstruktor rozdělí instanci třídy X na dvě části, které dohromady dávají původní objekt. Jedna část je přiřazena do x, druhá část je přiřazena do nově vzniklé instance. • Schopnost dělit-se musí mít zejména rozsahy, ale také třídy, jejichž instance běží paralelně a přitom nějakým způsobem interagují, např. třídy realizující paralelní redukci. split • Speciální třída definovaná za účelem odlišení dělicího konstruktoru od kopírovacího konstruktoru. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 33/54 Koncept rozsahu Požadavky na třídu realizující rozsah o Kopírovací konstruktor R::R (const R&) • Dělicí konstruktor R::R (const R&, split) • Destruktor R::~R () • Test na prázdnost rozsahu bool R::empty() const • Test na schopnost dalšího rozdělení bool R: :is_divisible() const Předdefinované šablony rozsahů • Jednodimenzionální: biocked_range • Dvoudimenzionální: blocked_range2d IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 34/54 TBB: blockecLrange blockecLrange template class blockecLrange; 9 Reprezentuje nadále dělitelný otevřený interval [i, j). Požadavky na třídu Value specializující blocked_range o Kopírovací konstruktor Value::Value (const Valuefe) • Destruktor Value::"Value () • Operátor porovnání bool Value::operátor<(const Valuefe i, const Valuefe j) • Počet objektů v daném rozsahu (operátor —) size_t Value::operator-(const Valuefe i, const Valuefe j) • /c-tý následný objekt po / (operátor +) Value Value::operator*(const Valuefe i) IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 35/54 TBB: blockecLrange Použití blocked_range • Nejdůležitější metodou je konstruktor. • Konstruktor specifikuje interval rozsahu a velikost největšího dále nedělitelného sub-intervalu: • blockecLrange(Value begin, Value end [, size_t grainsize] ) Typická specializace 9 blocked_range • Příklad: blocked_range(5, 17, 2) • Příklad: blocked_range(0, 11) IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 36/54 parallel_f or 9 template void parallel_for( const Rangefe range, const Bodyfe body); Požadavky na třídu realizující tělo cyklu o Kopírovací konstruktor Body::Body (const Bodyfe) • Destruktor Body::"Body () • Aplikátor těla cyklu na daný rozsah - operátor () void Body::operator()(Rangefe range) const IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 37/54 TBB: paralleLreduce parallel_reduce • template void parallel_reduce( const Rangefe range, const Bodyfe body); Požadavky na třídu realizující tělo redukce • Dělicí konstruktor Body::Body (const Bodyfe, split) 9 Destruktor Body::"Body () • Funkce realizující redukci nad daným rozsahem - operátor () void Body::operator()(Rangefe range) o Funkce realizující redukci hodnot z různých rozsahů void Body:: join(Bodyfe to_be_joined) IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 38/54 Možnosti dělení Třída Partitioner o Paralelní konstrukce mají třetí volitelný parametr, který specifikuje strategii dělení rozsahu. • parallel_f or Předdefinované strategie • simple.partitioner • Rekurzivně dělí rozsah až na dále nedělitelné intervaly. • Při použití blocked_range je volba grainsize klíčová pro vyvážení potenciálu a režie paralelizace. • auto_partitioner • Automatické dělení, které zohledňuje zatížení vláken. • Při použití blocked_range volí rozsahy větší, než je grainsize a tyto dělí pouze do té doby, než je dosaženo rozumného vyvážení zátěže. Volba minimální velikosti grainsize nezpůsobí nadbytečnou režii spojenou s paralelizací. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 39/54 Paralelně přistupované kontejnery - vector a queue concurrent_queue • template concurret_queue • Fronta, ke které může souběžně přistupovat více vláken. • Velikost fronty je dána počtem operací vložení bez počtu operací výběru. Záporná hodnota značí čekající operace výběru. • Definuje sekvenční, iterátory, nedoporučuje seje používat. concurrent_vector O tempate concurrent_vector • Zvětšovatelné pole prvků, ke kterému je možné souběžně přistupovat z více vláken a provádět souběžně zvětšování pole a přístup k již uloženým prvkům. o Nad vektorem lze definovat rozsah a provádět skrze něj paralelně operace s prvky uloženými v poli. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 40/54 Paralelně přistupované kontejnery - hash_map c oncur r ent Jiash _map • template class concurrent_hash_map; Mapa, ve které je možné paralelně hledat, mazat a vkládat. Požadavky na třídu HashCompare o Kopírovací konstruktor HashCompare::HashCompare (const HashComparefe) 9 Destruktor HashCompare::"HashCompare () 9 Test na ekvivalenci objektů bool HashCompare::equal(const Key& i, const Key& j)const • Výpočet hodnoty h eso vací funkce size_t HashCompare::hash(const Key& k) IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 41/54 Paralelně přistupované kontejnery - hash_map Objekty pro přístup k datům v concurrent_hash_map 9 Přístup k párům Klíč-Hodnota je skrze přistupovací třídy. • accessor - pro přístup v režimu read/write • const_accessor - pro přístup pouze v režimu read • Použití přistupovacích objektů umožňuje korektní paralelní přístup ke sdíleným datům. Příklad použití přistupovacího objektu 9 typedef concurrent_hash_map MyTable; MyTable table; MyTable::accessor a; table.insert( a, 4 ); a->second += 1; a.releaseQ ; IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 42/54 Paralelně přistupované kontejnery - hash_map Metody pro práci s concurrent Jiashjnap • bool find(const_accessor& result, const Key& key) const • bool find(accessor& result, const Key& key) • bool insert(const_accessor& result, const Key& key) O bool erase(const Key& key) Další způsoby použití o Iterátory pro procházení mapy. o Lze definovat rozsahy a s nimi pracovat paralelně. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 43/54 C++11 IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 44/54 CH—hli a vláknování Pozorování 9 C++11 má definované příkazy pro podporu vláken. • Není třeba používat externí knihovny jako je POSIX Thread. Jak je to možné o C++11 definuje virtuální výpočetní stroj. • Veškerá sémantika příkazů se odkazuje na tento virtuální výpočetní stroj. • Virtuální výpočetní stroje je paralelní, příkazy související s podporou vláken mohou bý součástí jazyka. • Přenos sémantiky z virtuálního výpočetního stroje na reálný HW je na zodpovědnosti překladače. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 45/54 Příklad - Vlákna a mutexy v C++11 #include #include std::mutex mylock; void func(int& a) { mylock.1ock(); a++; mylock.unlockO ; } int main() { int a = 42; std::thread tl(func, std::ref(a)); std::thread t2(func, std::ref(a)); tl.join(); t2.join(); std::cout << a « std::endl; return 0; } IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 46/54 Zamykaní v C++11 Potencionální riziko uváznutí • Jazyk s plnou podporou mechanismu výjimek. o Vyvolání výjimky v okamžiku, kdy je vlákno v kritické sekci (uvnitř mutexu) pravděpodobně způsobí, že nebude vláknem volána metoda odemykající zámek svázaný s kritickou sekcí. Řešení • Využití principu RAM a OOP. 9 Zamčení mutexu realizováno vytvořením lokální instance vhodné předdefinované zamykací třídy. • Odemykání umístěno do destruktoru této třídy. • Destruktor je proveden v okamžiku opuštění rozsahu platnosti daného objektu. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 47/54 RAM zamykáni v C++11 Třída lock_guard • Obalení standardního zámku v RAM stylu. • Mutex na pozadí nelze „předat" jinému vláknu, nevhodné pro podmínkové proměnné. • Příklad použití: std::mutex m; void func(int& a) { std: :lock_guard l(m) ; a++; } Třída unique_lock • Obecnější předatelné RAM obalení mutexu. • Doporučené pro použití s podmínkovými proměnnými. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 48/54 Podporované aspekty Podpora vláknování v C++11 o Vlákna. 9 Mutexy a RAM zámky. • Podmínkové proměnné. 9 Sdílené futures (místa uložení dosud nespočítané hodnoty). Rozcestník • http://en.cppreference.com/w/cpp/thread. Jiné rychlé přehledy 9 http: //www. codeproj ect. com/Articles/598695/Cplusplus-threads-locks- and- condition-variables 9 http://stackoverflow.com/questions/6319146/ c 11-introduced-a-standardized-memory-model-what-does-it-mean-and-how-is-it-g IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 49/54 Atomicita zápisů Neatomicky • int x,y; Thread 2 cout « y « " "; cout << x << endl; Thread 1 x = 17; y = 37; • Nemá definované chování. Správně atomicky • atomic x, y; Thread 1 x.store(17); y.store(37); Thread 2 cout << y.loadO << cout << x.loadQ << endl; • Chování je definované, možné výstupy: 0 0, 0 17, 37 17. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 50/54 Práce s paměťovým modelem v C++11 Paměťový model • Implicitní chování zachovává sekvenční konzistenci (automaticky vkládá odpovídající paměťové bariéry) • Riziko neefektivního kódu. Příklad 1 • atomic x, y; Thread 1 x. store (17, memory_order_relaxed) ; y. store (37, memory_order_relaxed) ; Thread 2 cout << y. load (memory _order .relaxed) << " "; cout << x. load (memory_order.relaxed) << endl; Sémantika povoluje v tomto případě i výstup: 37 0. IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 51/54 Práce s paměťovým modelem v C++11 - pokračovaní Paměťový model • Implicitní chování zachovává sekvenční konzistenci (automaticky vkládá odpovídající paměťové bariéry) • Riziko neefektivního kódu. Příklad 2 • atomic x, y; Thread 1 x. store (17,memory_order_release) ; y. store (37, memory _order_release) ; Thread 2 cout << y. load (memory_order.acquire) << " "; cout << x. load (memory_order.acquire) << endl; Acquire nepřeuspořádá operace load, Release - store IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 52/54 Jiné přístupy IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 53/54 Paralelní for cyklus Paralelní for cyklus • Nejčastější a nejednoduší metoda paralelizace. • Datová paralelizace. Jak a kde lze řešit paralelní for cyklus • http://parallel-for.sourceforge.net/ IB109 Návrh a implementace paralelních systémů: Pokročilá rozhraní pro implementaci paralelních aplikací str. 54/54