Nástroje pro synchronizaci vláken v OS Windows (kritické sekce, mutexy, semafory, události). Synchronizační objekty ve Windows Synchronizace procesů pomocí událostí

Přednáška č. 9. Synchronizace procesů a vláken

1. Cíle a prostředky synchronizace.

2. Synchronizační mechanismy.

1.Cíle a prostředky synchronizace

Existuje poměrně široká třída nástrojů operačního systému, které zajišťují vzájemnou synchronizaci procesů a vláken. Potřeba synchronizace vláken vzniká pouze ve víceprogramovém operačním systému a je spojena se společným využitím hardwaru a informačních zdrojů počítačového systému. Synchronizace je nezbytná, aby se zabránilo závodům a uváznutí při výměně dat mezi vlákny, sdílení dat a přístupu k procesoru a I/O zařízením.

V mnoha operačních systémech se tyto nástroje nazývají meziprocesové komunikační nástroje (IPC), což odráží historické prvenství pojmu „proces“ ve vztahu k pojmu „vlákno“. Nástroje IPC obvykle zahrnují nejen nástroje pro synchronizaci mezi procesy, ale také nástroje pro výměnu dat mezi procesy.

Provádění vlákna v multiprogramovém prostředí je vždy asynchronní. Je velmi obtížné s úplnou jistotou říci, v jaké fázi provádění bude proces v určitém okamžiku. Ani v režimu jednoho programu není vždy možné přesně odhadnout čas potřebný k dokončení úkolu. Tato doba v mnoha případech výrazně závisí na hodnotě zdrojových dat, která ovlivňuje počet cyklů, směr větvení programu, dobu provádění I/O operací atd. Vzhledem k tomu, že zdrojová data v různých časech, kdy je úloha spouštěné se mohou lišit, stejně jako doba provádění jednotlivých fází a úkolu jako celku je velmi nejistá hodnota.


Ještě nejistější je doba provádění programu v multiprogramovém systému. Okamžiky, kdy jsou vlákna přerušena, čas, který stráví ve frontách na sdílené zdroje, pořadí, ve kterém jsou vlákna vybírána k provedení – všechny tyto události jsou výsledkem souběhu mnoha okolností a lze je interpretovat jako náhodné. V nejlepším případě lze odhadnout pravděpodobnostní charakteristiky výpočetního procesu, například pravděpodobnost jeho dokončení za dané časové období.

Vlákna tedy v obecném případě (když programátor neučinil speciální opatření k jejich synchronizaci) proudí nezávisle, vzájemně asynchronně. To platí jak pro vlákna ve stejném procesu provádějící společný programový kód, tak pro vlákna v různých procesech, z nichž každé provádí svůj vlastní program.

Jakákoli interakce mezi procesy nebo vlákny souvisí s jejich synchronizace, která spočívá v koordinaci jejich rychlostí pozastavením toku až do výskytu určité události a následnou aktivací, když tato událost nastane. Synchronizace je jádrem jakékoli interakce vláken, ať už zahrnuje sdílení zdrojů nebo výměnu dat. Přijímající vlákno by například mělo přistupovat k datům až poté, co je uloží do vyrovnávací paměti odesílající vlákno. Pokud přijímající vlákno přistupovalo k datům před vstupem do vyrovnávací paměti, musí být pozastaveno.

Při sdílení hardwarových prostředků je také naprosto nezbytná synchronizace. Když například aktivní vlákno potřebuje přístup k sériovému portu a jiné vlákno, které je aktuálně ve stavu čekání, pracuje s tímto portem ve výhradním režimu, OS pozastaví aktivní vlákno a neaktivuje ho, dokud port, který potřebuje. je zdarma . Často je také nutná synchronizace s událostmi mimo počítačový systém, například reakce na stisknutí kombinace kláves Ctrl+C.

Každou sekundu dochází v systému ke stovkám událostí souvisejících s alokací a uvolňováním zdrojů a operační systém musí mít spolehlivé a účinné prostředky, které by mu umožnily synchronizovat vlákna s událostmi vyskytujícími se v systému.

K synchronizaci vláken aplikačního programu může programátor použít jak své vlastní synchronizační nástroje a techniky, tak nástroje operačního systému. Například dvě vlákna stejného aplikačního procesu mohou koordinovat svou práci pomocí globální booleovské proměnné, která je jim k dispozici a která je nastavena na jednu, když dojde k nějaké události, například jedno vlákno produkuje data nezbytná pro to, aby druhé mohlo pokračovat v práci. V mnoha případech jsou však synchronizační prostředky poskytované operačním systémem ve formě systémových volání efektivnější nebo dokonce jediné možné. Vlákna patřící k různým procesům si tedy nemohou vzájemně zasahovat do práce. Bez zprostředkování operačního systému se nemohou vzájemně pozastavit ani se vzájemně upozorňovat na výskyt nějaké události. Synchronizační nástroje využívá operační systém nejen k synchronizaci aplikačních procesů, ale i pro své interní potřeby.

Vývojáři operačních systémů obvykle poskytují širokou škálu synchronizačních nástrojů, které mají k dispozici programátoři aplikací a systémů. Tyto nástroje mohou tvořit hierarchii, kdy složitější jsou stavěny na základě jednodušších nástrojů, a také být funkčně specializované, například nástroje pro synchronizaci vláken jednoho procesu, nástroje pro synchronizaci vláken různých procesů při výměně dat atd. Často se funkce synchronizace různých systémových volání překrývají, takže programátor může použít několik volání k vyřešení jednoho problému v závislosti na svých osobních preferencích.


Potřeba synchronizace a závodu

Zanedbání problémů se synchronizací ve vícevláknovém systému může vést k nesprávnému řešení problému nebo dokonce k pádu systému. Vezměme si například (obr. 4.16) úkol udržovat databázi klientů určitého podniku. Každému klientovi je přiřazen samostatný záznam v databázi, která obsahuje mimo jiné pole Objednávka a Platba. Program, který spravuje databázi, je navržen jako jeden proces, který má několik vláken, včetně vlákna A, které zadává informace o přijatých objednávkách od zákazníků do databáze, a vlákna B, které do databáze zaznamenává informace o platbách zákazníků za faktury. Obě tato vlákna spolupracují na společném databázovém souboru pomocí stejných algoritmů, které zahrnují tři kroky.

2. Zadejte novou hodnotu do pole Objednávka (pro tok A) nebo Platba (pro tok B).

3. Vraťte upravený záznam do souboru databáze.

https://pandia.ru/text/78/239/images/image002_238.gif" width="505" height="374 src=">

Rýže. 4.17. Vliv relativních rychlostí proudění na výsledek řešení úlohy

Kritická sekce

Důležitým konceptem synchronizace vláken je koncept „kritické sekce“ programu. Kritická sekce je součástí programu, jehož výsledek provádění se může nepředvídatelně změnit, pokud jsou proměnné související s touto částí programu změněny jinými vlákny, zatímco provádění této části ještě není dokončeno. Kritický úsek je vždy definován ve vztahu k určitému kritická data pokud se změní nekoordinovaným způsobem, může dojít k nežádoucím účinkům. V předchozím příkladu byly kritickými daty záznamy databázových souborů. Všechna vlákna pracující s kritickými daty musí mít definovanou kritickou sekci. Všimněte si, že v různých vláknech se kritická sekce obecně skládá z různých sekvencí příkazů.

Chcete-li eliminovat vliv závodů na kritická data, je nutné zajistit, aby v kritické sekci spojené s těmito daty bylo vždy pouze jedno vlákno. Nezáleží na tom, zda je toto vlákno v aktivním nebo pozastaveném stavu. Tato technika se nazývá vzájemné vyloučení. Operační systém používá různé způsoby implementace vzájemného vyloučení. Některé metody jsou vhodné pro vzájemné vyloučení, když do kritické sekce vstupují pouze vlákna jednoho procesu, zatímco jiné mohou poskytnout vzájemné vyloučení pro vlákna různých procesů.

Nejjednodušší a zároveň neefektivní způsob, jak zajistit vzájemné vyloučení, je, že operační systém umožní vláknu zakázat jakákoli přerušení, když je v kritické sekci. Tato metoda se však prakticky nepoužívá, protože je nebezpečné důvěřovat uživatelskému vláknu pro řízení systému - může to zaměstnávat procesor na dlouhou dobu, a pokud vlákno selže v kritické sekci, zhroutí se celý systém, protože přerušení nebudou nikdy povolena.

2. Synchronizační mechanismy.

Blokování proměnných

K synchronizaci vláken jednoho procesu může aplikační programátor použít globální blokovací proměnné. Programátor pracuje s těmito proměnnými, ke kterým mají přímý přístup všechna vlákna procesu, aniž by se uchýlil k systémovému volání OS.

Což by zakázalo přerušení během celé operace ověření a instalace.

Implementace vzájemného vyloučení výše popsaným způsobem má značnou nevýhodu: během doby, kdy je jedno vlákno v kritické sekci, další vlákno, které potřebuje stejný zdroj a má přístup k procesoru, bude nepřetržitě dotazovat blokující proměnnou, čímž plýtvá časem procesoru. k němu přidělené, které by se dalo použít ke spuštění nějakého jiného vlákna. Aby se tento nedostatek odstranil, mnoho operačních systémů poskytuje speciální systémová volání pro práci s kritickými sekcemi.

Na Obr. Obrázek 4.19 ukazuje, jak tyto funkce implementují vzájemné vyloučení v operačním systému Windows NT. Před zahájením úpravy důležitých dat vlákno vydá systémové volání EnterCriticalSection(). Toto volání nejprve provede, stejně jako v předchozím případě, kontrolu blokovací proměnné odrážející stav kritického zdroje. Pokud systémové volání určí, že zdroj je zaneprázdněn (F(D) = 0), na rozdíl od předchozího případu neprovede cyklické dotazování, ale uvede vlákno do stavu čekání (D) a zaznamená, že toto vlákno by měl být aktivován, jakmile bude příslušný zdroj dostupný. Vlákno, které aktuálně používá tento prostředek, musí po opuštění kritické sekce provést systémovou funkci LeaveCriticalSectionO, v důsledku čehož blokující proměnná nabývá hodnoty odpovídající volnému stavu zdroje (F(D) = 1), a operační systém prohlédne frontu těch, kteří čekají na vlákna tohoto prostředku, a přesune první vlákno z fronty do stavu připravenosti.

Režijní náklady" href="/text/category/nakladnie_rashodi/" rel="bookmark">Režijní náklady OS na implementaci funkce vjezdu a výstupu z kritické sekce mohou přesáhnout dosažené úspory.

Semafory

Zobecněním blokovacích proměnných jsou tzv Dijkstrovské semafory. Namísto binárních proměnných navrhl Dijkstra použití proměnných, které mohou mít nezáporné celočíselné hodnoty. Takové proměnné, používané k synchronizaci výpočetních procesů, se nazývají semafory.

Pro práci se semafory jsou zavedena dvě primitiva, tradičně označovaná P a V. Nechť proměnná S představuje semafor. Potom jsou akce V(S) a P(S) definovány následovně.

* V(S): Proměnná S se zvýší o 1 jako jediná akce. Vzorkování, sestavení a skladování nelze přerušit. Během provádění této operace k proměnné S nemají přístup jiná vlákna.

* P(S): Pokud je to možné, sníží S o 1. Je-li 5=0 a není možné snížit S a přitom zůstat v oblasti nezáporných celočíselných hodnot, pak operace volání vlákna P čeká, dokud nebude toto snížení možné. Nedělitelná operace je také úspěšná kontrola a dekrementace.

Během provádění primitiv V a P nejsou povolena žádná přerušení.

Ve speciálním případě, kdy semafor S může nabývat pouze hodnot 0 a 1, se stává blokující proměnnou, která se z tohoto důvodu často nazývá binární semafor. Aktivita P má potenciál uvést vlákno, které ji provádí, do stavu čekání, zatímco aktivita V může za určitých okolností probudit jiné vlákno, které bylo pozastaveno aktivitou P.

Podívejme se na použití semaforů na klasickém příkladu interakce dvou vláken běžících v režimu multiprogramování, z nichž jedno zapisuje data do bufferu a druhé je čte z bufferu. Nechte fond vyrovnávacích pamětí sestávat z N vyrovnávacích pamětí, z nichž každý může obsahovat jednu položku. Obecně platí, že vlákno zápisu a vlákno čtečky mohou mít různé rychlosti a přistupovat k fondu vyrovnávacích pamětí s různou intenzitou. V jednom období může rychlost zápisu překročit rychlost čtení, v jiném - naopak. Pro správnou spolupráci se vlákno pro zápis musí pozastavit, když jsou všechny vyrovnávací paměti zaneprázdněné, a probudit se, když se uvolní alespoň jedna vyrovnávací paměť. Naproti tomu vlákno čtečky by se mělo pozastavit, když jsou všechny vyrovnávací paměti prázdné, a probudit se, když se objeví alespoň jeden zápis.

Zaveďme dva semafory: e - počet prázdných bufferů a f - počet naplněných bufferů a v počátečním stavu e = N, a f = 0. Potom lze provoz vláken se společným buffer poolem popsat následovně (obr. 4.20).

Vlákno zapisovače nejprve provede operaci P(e), pomocí které zkontroluje, zda ve fondu vyrovnávacích pamětí nejsou nějaké prázdné vyrovnávací paměti. V souladu se sémantikou operace P, pokud je semafor e roven 0 (to znamená, že v tuto chvíli nejsou žádné volné vyrovnávací paměti), vlákno zapisovače vstoupí do stavu čekání. Pokud je hodnota e kladné číslo, sníží počet volných vyrovnávacích pamětí, zapíše data do dalšího volného zásobníku a poté zvýší počet obsazených zásobníků pomocí operace V(f). Podobně se chová i čtenářské vlákno s tím rozdílem, že začíná kontrolou plných vyrovnávacích pamětí a po načtení dat zvyšuje počet volných vyrovnávacích pamětí.

DIV_ADBLOCK860">

Jako blokovací proměnná lze také použít semafor. Ve výše uvedeném příkladu, abychom eliminovali kolize při práci s oblastí sdílené paměti, budeme předpokládat, že zápis a čtení z vyrovnávací paměti jsou kritickými sekcemi. Vzájemné vyloučení zajistíme pomocí binárního semaforu b (obr. 4.21). Obě vlákna po kontrole dostupnosti vyrovnávacích pamětí musí zkontrolovat dostupnost kritické sekce.

https://pandia.ru/text/78/239/images/image007_110.jpg" width="495" height="639 src=">

Rýže. 4.22. Výskyt uváznutí během provádění programu

POZNÁMKA

Zablokování je třeba odlišit od jednoduchých front, i když obojí vzniká, když jsou zdroje sdíleny, a vypadají podobně: vlákno je pozastaveno a čeká, až se zdroj uvolní. Fronta je však normálním jevem, neodmyslitelným znakem vysokého využití zdrojů, když požadavky přicházejí náhodně. Fronta se objeví, když zdroj není momentálně dostupný, ale bude po nějaké době uvolněn, což umožní vláknu pokračovat ve vykonávání. Patová situace, jak její název napovídá, je poněkud neřešitelná situace. Nezbytnou podmínkou pro zablokování je, že vlákno potřebuje několik zdrojů najednou.

V uvažovaných příkladech bylo zablokování tvořeno dvěma vlákny, ale více vláken se může vzájemně blokovat. Na Obr. Obrázek 2.23 ukazuje takové rozdělení zdrojů Ri mezi několik vláken Tj, které vedlo k výskytu uváznutí. Šipky označují požadavky toku na zdroje. Plná šipka znamená, že odpovídající prostředek byl přidělen vláknu, a tečkovaná šipka spojuje vlákno se zdrojem, který je potřeba, ale zatím nemůže být přidělen, protože je obsazený jiným vláknem. Například vlákno T1 potřebuje k provádění práce prostředky R1 a R2, z nichž je přidělen pouze jeden - R1, a prostředek R2 je držen vláknem T2. Žádné ze čtyř vláken zobrazených na obrázku nemůže pokračovat ve své práci, protože k tomu nemají všechny potřebné zdroje.

Neschopnost vláken dokončit započatou práci kvůli uváznutí snižuje výkon výpočetního systému. Proto je věnována velká pozornost problému předcházení uváznutí. V případě, že k zablokování skutečně dojde, musí systém operátorovi poskytnout prostředky, pomocí kterých dokáže rozpoznat zablokování a odlišit ho od běžného bloku z důvodu dočasné nedostupnosti zdrojů. A konečně, pokud je diagnostikováno zablokování, jsou potřeba prostředky k odstranění zablokování a obnovení normálního výpočetního procesu.

Vlastník" href="/text/category/vladeletc/" rel="bookmark">vlastník, nastaví jej do nesignalizovaného stavu a vstoupí do kritické sekce. Poté, co vlákno dokončí práci s kritickými daty, „poskytne up“ mutex, čímž jej uvedete do signalizovaného stavu. V tuto chvíli je mutex volný a nepatří žádnému vláknu. Pokud nějaké vlákno čeká na své uvolnění, stává se dalším vlastníkem tohoto mutexu, v současně mutex přejde do nesignalizovaného stavu.

Objekt události (v tomto případě se slovo „událost“ používá v úzkém smyslu jako označení konkrétního typu synchronizačního objektu) se obvykle nepoužívá k přístupu k datům, ale k upozornění ostatních vláken na dokončení některých akcí. Nechť je například v některé aplikaci práce organizována tak, že jedno vlákno čte data ze souboru do vyrovnávací paměti a další vlákna tato data zpracovávají, pak první vlákno čte novou část dat a další vlákna zpracovat znovu a tak dále. Na začátku provádění nastaví první vlákno objekt události do nesignalizovaného stavu. Všechna ostatní vlákna zavolala Wait(X), kde X je ukazatel události, a jsou v pozastaveném stavu a čekají, až tato událost nastane. Jakmile je vyrovnávací paměť plná, první vlákno to oznámí operačnímu systému voláním Set(X). Operační systém prohledá frontu čekajících vláken a aktivuje všechna vlákna, která čekají na tuto událost.

Signály

Signál Umožňuje úloze reagovat na událost, jejímž zdrojem může být operační systém nebo jiná úloha. Signály zahrnují přerušení úlohy a provedení předem určených akcí. Signály mohou být generovány synchronně, to znamená jako výsledek práce samotného procesu, nebo mohou být odeslány do procesu jiným procesem, tedy generovány asynchronně. Synchronní signály nejčastěji pocházejí ze systému přerušení procesoru a indikují akce procesu, které jsou blokovány hardwarem, jako je dělení nulou, chyba adresování, narušení ochrany paměti atd.

Příkladem asynchronního signálu je signál z terminálu. Mnoho operačních systémů umožňuje rychlé odstranění procesu z provádění. K tomu může uživatel stisknout určitou kombinaci kláves (Ctrl+C, Ctrl+Break), v důsledku čehož OS vygeneruje signál a odešle jej aktivnímu procesu. Signál může dorazit kdykoli během provádění procesu (to znamená, že je asynchronní), což vyžaduje okamžité ukončení procesu. V tomto případě je odpovědí na signál bezpodmínečné dokončení procesu.

V systému lze definovat sadu signálů. Programový kód procesu, který přijal signál, jej může buď ignorovat, nebo na něj reagovat standardní akcí (například ukončením), nebo provádět specifické akce definované aplikačním programátorem. V druhém případě je nutné v kódu programu zajistit speciální systémová volání, pomocí kterých je operační systém informován, jaký postup by měl být proveden v reakci na přijetí konkrétního signálu.

Signály zajišťují logickou komunikaci mezi procesy a mezi procesy a uživateli (terminály). Protože odeslání signálu vyžaduje znalost identifikátoru procesu, interakce prostřednictvím signálů je možná pouze mezi souvisejícími procesy, které mohou získat informace o identifikátorech ostatních.

V distribuovaných systémech skládajících se z více procesorů, z nichž každý má svou vlastní RAM, jsou zamykací proměnné, semafory, signály a další podobné funkce založené na sdílené paměti nevhodné. V takových systémech lze synchronizace dosáhnout pouze výměnou zpráv.

Proces je instancí programu načteného do paměti. Tato instance může vytvářet vlákna, což jsou sekvence instrukcí k provedení. Je důležité pochopit, že to nejsou procesy, které běží, ale spíše vlákna.

Navíc každý proces má alespoň jedno vlákno. Toto vlákno se nazývá hlavní (hlavní) vlákno aplikace.

Vzhledem k tomu, že téměř vždy existuje mnohem více vláken, než je fyzických procesorů, které je spouštějí, nejsou vlákna ve skutečnosti spouštěna současně, ale postupně (čas procesoru je rozdělen mezi vlákna). Ale přepínání mezi nimi se děje tak často, že se zdá, že běží paralelně.

V závislosti na situaci mohou být vlákna ve třech stavech. Za prvé, vlákno se může spustit, když je mu přidělen čas CPU, tj. může být ve stavu aktivity. Za druhé může být neaktivní a čeká na přidělení procesoru, tzn. být ve stavu připravenosti. A je tu ještě třetí, také velmi důležitý stav – stav blokování. Když je vlákno zablokováno, není přiděleno vůbec. Typicky je blok umístěn při čekání na nějakou událost. Když nastane tato událost, vlákno se automaticky přesune z blokovaného stavu do stavu připravenosti. Pokud například jedno vlákno provádí výpočty a druhé musí čekat na výsledky, aby je uložilo na disk. Druhý by mohl používat smyčku jako "while(!isCalcFinished) continue;", ale v praxi lze snadno ověřit, že při provádění této smyčky je procesor 100% vytížený (říká se tomu aktivní čekání). Těmto cyklům je třeba se pokud možno vyhnout, v nichž zajišťovací mechanismus poskytuje neocenitelnou pomoc. Druhé vlákno se může zablokovat, dokud první vlákno nevyvolá událost označující, že čtení je dokončeno.

Synchronizace vláken v OS Windows

Windows implementuje preemptivní multitasking – to znamená, že systém může kdykoli přerušit provádění jednoho vlákna a převést řízení na jiné. Dříve se ve Windows 3.1 používala organizační metoda zvaná kooperativní multitasking: systém čekal, až mu řízení předá samotné vlákno, a proto v případě zamrznutí jedné aplikace musel být počítač restartován.

Všechna vlákna patřící ke stejnému procesu sdílejí některé společné zdroje – například adresní prostor RAM nebo otevřené soubory. Tyto prostředky patří celému procesu, a tedy každému jeho vláknu. Každé vlákno tedy může s těmito prostředky pracovat bez jakýchkoli omezení. Ale... Pokud jedno vlákno ještě nedokončilo práci s nějakým sdíleným prostředkem a systém se přepne do jiného vlákna pomocí stejného prostředku, pak se výsledek práce těchto vláken může extrémně lišit od toho, co bylo zamýšleno. Takové konflikty mohou také nastat mezi vlákny, které patří do různých procesů. K tomuto problému dochází vždy, když dva nebo více podprocesů sdílí jakýkoli sdílený prostředek.

Příklad. Nesynchronizovaná vlákna: Pokud dočasně pozastavíte výstupní vlákno (pauza), vlákno naplnění pole na pozadí bude pokračovat v běhu.

#zahrnout #zahrnout int a; RUKOJEŤ hThr; unsigned long uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( for (i=0; i<5; i++) a[i] = num; num++; } } int main(void) { hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) printf("%d %d %d %d %d\n", a, a, a, a, a); return 0; }

To je důvod, proč je zapotřebí mechanismus, který vláknům umožní koordinovat svou práci se sdílenými prostředky. Tento mechanismus se nazývá mechanismus synchronizace vláken.

Tento mechanismus je sada objektů operačního systému, které jsou vytvářeny a spravovány programově, jsou společné všem vláknům v systému (některé jsou sdíleny vlákny patřícími ke stejnému procesu) a používají se ke koordinaci přístupu ke zdrojům. Prostředky může být cokoli, co může být sdíleno dvěma nebo více vlákny – diskový soubor, port, položka databáze, objekt GDI a dokonce i globální programová proměnná (k níž mohou přistupovat vlákna patřící ke stejnému procesu).

Existuje několik synchronizačních objektů, z nichž nejdůležitější jsou mutex, kritická sekce, událost a semafor. Každý z těchto objektů implementuje svou vlastní metodu synchronizace. Také samotné procesy a vlákna mohou být použity jako synchronizační objekty (když jedno vlákno čeká na dokončení jiného vlákna nebo procesu); stejně jako soubory, komunikační zařízení, vstup z konzoly a upozornění na změny.

Jakýkoli synchronizační objekt může být v tzv. signálovém stavu. Pro každý typ objektu má tento stav jiný význam. Vlákna mohou kontrolovat aktuální stav objektu a/nebo čekat na změnu tohoto stavu a koordinovat tak své akce. Tím je zajištěno, že když vlákno pracuje se synchronizačními objekty (vytváří je, mění stav), systém nepřeruší jeho provádění, dokud tuto akci nedokončí. Všechny finální operace se synchronizačními objekty jsou tedy atomické (nedělitelné.

Práce se synchronizačními objekty

Pro vytvoření jednoho nebo druhého synchronizačního objektu se zavolá speciální funkce WinAPI typu Create... (například CreateMutex). Toto volání vrací popisovač objektu (HANDLE), který mohou používat všechna vlákna patřící do tohoto procesu. K synchronizačnímu objektu je možné přistupovat z jiného procesu - buď zděděním handle tohoto objektu, nebo nejlépe pomocí volání funkce otevření objektu (Otevřít...). Po tomto volání proces obdrží popisovač, který lze později použít k práci s objektem. Objekt, pokud není určen k použití v rámci jednoho procesu, musí být pojmenován. Názvy všech objektů musí být různé (i když jsou různého typu). Nemůžete například vytvořit událost a semafor se stejným názvem.

Pomocí existujícího deskriptoru objektu můžete určit jeho aktuální stav. To se provádí pomocí tzv. čekající funkce. Nejčastěji používanou funkcí je WaitForSingleObject. Tato funkce má dva parametry, z nichž první je handle objektu, druhý je časový limit v ms. Funkce vrátí WAIT_OBJECT_0, pokud je objekt signalizován, WAIT_TIMEOUT, pokud vypršel časový limit, a WAIT_ABANDONED, pokud objekt mutex nebyl uvolněn před ukončením jeho vlastnického vlákna. Pokud je časový limit zadán jako nula, funkce vrátí výsledek okamžitě, jinak čeká zadanou dobu. Pokud se stav objektu stane signálem před uplynutím této doby, funkce vrátí WAIT_OBJECT_0, jinak funkce vrátí WAIT_TIMEOUT. Pokud je jako čas uvedena symbolická konstanta INFINITE, funkce bude čekat neomezeně dlouho, dokud se stav objektu nestane signálem.

Velmi důležitým faktem je, že volání čekající funkce blokuje aktuální vlákno, tzn. Když je vlákno v nečinném stavu, není mu přidělen žádný čas CPU.

Kritické úseky

Objekt kritické sekce pomáhá programátorovi izolovat sekci kódu, kde vlákno přistupuje ke sdílenému prostředku, a zabránit souběžnému použití zdroje. Před použitím prostředku vlákno vstoupí do kritické sekce (volá funkci EnterCriticalSection). Pokud se jakékoli jiné vlákno pokusí vstoupit do stejné kritické sekce, jeho provádění se pozastaví, dokud první vlákno neopustí sekci voláním LeaveCriticalSection. Používá se pouze pro vlákna jednoho procesu. Pořadí vstupu do kritické sekce není definováno.

K dispozici je také funkce TryEnterCriticalSection, která kontroluje, zda je kritická sekce aktuálně obsazena. S jeho pomocí nelze vlákno při čekání na přístup ke zdroji zablokovat, ale provést některé užitečné akce.

Příklad. Synchronizace vláken pomocí kritických sekcí.

#zahrnout #zahrnout CRITICAL_SECTION cs; int a; RUKOJEŤ hThr; unsigned long uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( EnterCriticalSection(&cs); for (i=0; i<5; i++) a[i] = num; num++; LeaveCriticalSection(&cs); } } int main(void) { InitializeCriticalSection(&cs); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { EnterCriticalSection(&cs); printf("%d %d %d %d %d\n", a, a, a, a, a); LeaveCriticalSection(&cs); } return 0; }

Vzájemná vyloučení

Objekty vzájemného vyloučení (mutexy, mutex - z MUTual EXclusion) umožňují koordinovat vzájemné vyloučení přístupu ke sdílenému zdroji. Stav signálu objektu (tj. stav "set") odpovídá časovému bodu, kdy objekt nepatří do žádného vlákna a může být "zachycen". Naopak stav "reset" (nesignál) odpovídá okamžiku, kdy některé vlákno již tento objekt vlastní. Přístup k objektu je udělen, když vlákno, které objekt vlastní, jej uvolní.

Dvě (nebo více) vlákna mohou vytvořit mutex se stejným názvem voláním funkce CreateMutex. První vlákno ve skutečnosti vytvoří mutex a další získají handle na již existující objekt. To umožňuje více vláknům získat popisovač ke stejnému mutexu, čímž se programátor nemusí starat o to, kdo vlastně mutex vytváří. Pokud je použit tento přístup, je vhodné nastavit příznak bInitialOwner na hodnotu FALSE, jinak budou určité potíže s určením skutečného tvůrce mutexu.

Více vláken může získat popisovač pro stejný mutex, což umožňuje komunikaci mezi procesy. Pro tento přístup lze použít následující mechanismy:

  • Podřízený proces vytvořený pomocí funkce CreateProcess může zdědit popisovač mutexu, pokud byl parametr lpMutexAttributes zadán při vytváření mutexu pomocí funkce CreateMutex.
  • Vlákno může získat duplikát existujícího mutexu pomocí funkce DuplicateHandle.
  • Vlákno může určit název existujícího mutexu při volání funkcí OpenMutex nebo CreateMutex.

Chcete-li deklarovat vzájemnou výjimku jako příslušnost k aktuálnímu vláknu, musíte zavolat jednu z čekajících funkcí. Vlákno, které objekt vlastní, jej může znovu získat, kolikrát chce (nevede to k samozamykání), ale musí jej uvolnit stejně kolikrát pomocí funkce ReleaseMutex.

Pro synchronizaci vláken jednoho procesu je efektivnější použít kritické sekce.

Příklad. Synchronizace vláken pomocí mutexů.

#zahrnout #zahrnout RUKOJEŤ hMutex; int a; RUKOJEŤ hThr; unsigned long uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hMutex, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseMutex(hMutex); } } int main(void) { hMutex=CreateMutex(NULL, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hMutex, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseMutex(hMutex); } return 0; }

Události

Objekty událostí se používají k upozornění čekajících vláken, že došlo k události. Existují dva typy událostí – s ručním a automatickým resetem. Ruční reset se provádí funkcí ResetEvent. Události ručního resetu se používají k upozornění více vláken najednou. Při použití události automatického resetu obdrží upozornění pouze jedno čekající vlákno a bude pokračovat ve vykonávání; zbytek bude nadále čekat.

Funkce CreateEvent vytvoří objekt události, SetEvent - nastaví událost do stavu signálu, ResetEvent - resetuje událost. Funkce PulseEvent nastaví událost a po obnovení vláken čekajících na tuto událost (všechny s ručním resetem a pouze jedno s automatickým resetem) ji resetuje. Pokud neexistují žádná čekající vlákna, PulseEvent jednoduše resetuje událost.

Příklad. Synchronizace vláken pomocí událostí.

#zahrnout #zahrnout HANDLE hEvent1, hEvent2; int a; RUKOJEŤ hThr; unsigned long uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hEvent2, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; SetEvent(hEvent1); } } int main(void) { hEvent1=CreateEvent(NULL, FALSE, TRUE, NULL); hEvent2=CreateEvent(NULL, FALSE, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hEvent1, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); SetEvent(hEvent2); } return 0; }

Semafory

Semaforový objekt je vlastně mutexový objekt s čítačem. Tento objekt se nechá „zajmout“ určitým počtem vláken. Poté bude „zachycení“ nemožné, dokud jej neuvolní jedno z vláken, které dříve „zachytilo“ semafor. Semafory se používají k omezení počtu vláken současně pracujících se zdrojem. Při inicializaci se do objektu přenese maximální počet vláken, po každém „zachycení“ se sníží počítadlo semaforu. Stav signálu odpovídá hodnotě čítače větší než nula. Když je čítač nula, semafor je považován za nenainstalovaný (reset).

Funkce CreateSemaphore vytvoří objekt semaforu s uvedením jeho maximální možné počáteční hodnoty, OpenSemaphore - vrátí deskriptor existujícího semaforu, semafor je zachycen pomocí čekajících funkcí a hodnota semaforu se sníží o jednu, ReleaseSemaphore - semafor je uvolněn se semaforem hodnota zvýšená o hodnotu uvedenou v čísle parametru.

Příklad. Synchronizace vláken pomocí semaforů.

#zahrnout #zahrnout RUKOJEŤ hSem; int a; RUKOJEŤ hThr; unsigned long uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hSem, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseSemaphore(hSem, 1, NULL); } } int main(void) { hSem=CreateSemaphore(NULL, 1, 1, "MySemaphore1"); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hSem, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseSemaphore(hSem, 1, NULL); } return 0; }

Chráněný přístup k proměnným

Existuje řada funkcí, které umožňují pracovat s globálními proměnnými ze všech vláken bez starostí o synchronizaci, protože tyto funkce to sledují samy - jejich provedení je atomické. Tyto funkce jsou InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd a InterlockedCompareExchange. Například funkce InterlockedIncrement atomicky zvyšuje hodnotu 32bitové proměnné o jednu, což je vhodné použít pro různé čítače.

Chcete-li získat úplné informace o účelu, použití a syntaxi všech funkcí WIN32 API, musíte použít systém nápovědy MS SDK obsažený v programovacích prostředích Borland Delphi nebo CBuilder a také MSDN, dodávaný jako součást programovacího systému Visual C.

Vlákna mohou být v jednom z několika stavů:

    Připraveno(připraveno) – nachází se ve fondu vláken čekajících na spuštění;

    Běh(provedení) - běžící na procesoru;

    Čekání(čekající), také nazývaný nečinný nebo pozastavený, pozastavený - ve stavu čekání, který končí spuštěním vlákna (stav běžící) nebo vstupem do stavu Připraveno;

    Ukončeno (completion) - provedení všech příkazů vlákna je dokončeno. Lze jej později smazat. Pokud není stream odstraněn, systém jej může vrátit do původního stavu pro pozdější použití.

Synchronizace vláken

Běžící vlákna často potřebují nějakým způsobem komunikovat. Pokud se například více vláken pokouší o přístup k některým globálním datům, pak každé vlákno musí chránit data před změnou jiným vláknem. Někdy jedno vlákno potřebuje vědět, kdy jiné vlákno dokončí úkol. Taková interakce je povinná mezi vlákny stejných i různých procesů.

Synchronizace vláken ( vlákno synchronizace) je obecný termín označující proces interakce a propojování vláken. Vezměte prosím na vědomí, že synchronizace vláken vyžaduje, aby samotný operační systém fungoval jako prostředník. Vlákna nemohou vzájemně interagovat bez její účasti.

Ve Win32 existuje několik metod pro synchronizaci vláken. Stává se, že v konkrétní situaci je jedna metoda výhodnější než jiná. Pojďme se na tyto metody v rychlosti podívat.

Kritické úseky

Jednou z metod synchronizace vláken je použití kritických sekcí. Toto je jediná metoda synchronizace vláken, která nevyžaduje jádro Windows. (Kritická sekce není objekt jádra.) Tuto metodu však lze použít pouze k synchronizaci vláken jednoho procesu.

Kritická část je část kódu, kterou může současně spustit pouze jedno vlákno. Pokud je kód použitý k inicializaci pole umístěn v kritické sekci, ostatní vlákna nebudou moci tuto sekci kódu zadat, dokud první vlákno nedokončí její provádění.

Před použitím kritické sekce ji musíte inicializovat pomocí procedury Win32 API InitializeCriticalSection(), která je definována (v Delphi) takto:

procedure InitializeCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Parametr IpCriticalSection je záznam typu TRTLCriticalSection, který je předán odkazem. Na přesné definici položky TRTLCriticalSection moc nezáleží, protože je nepravděpodobné, že byste se někdy potřebovali podívat na její obsah. Vše, co musíte udělat, je předat neinicializovaný záznam do parametru IpCtitical Section a tento záznam bude okamžitě naplněn procedurou.

Po vyplnění položky v programu můžete vytvořit kritickou sekci umístěním části jejího textu mezi volání funkcí EnterCriticalSection() a LeaveCriticalSection(). Tyto postupy jsou definovány takto:

procedure EnterCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

procedure LeaveCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Parametr IpCriticalSection, který je předán těmto procedurám, není nic jiného než záznam vytvořený procedurou InitializeCriticalSection().

Funkce Vstupte do kritické sekce zkontroluje, zda nějaké jiné vlákno již neprovádí kritickou sekci svého programu přidruženého k danému objektu kritické sekce. Pokud tomu tak není, vlákno získá oprávnění spustit svůj kritický kód, nebo lépe řečeno, není mu v tom bráněno. Pokud ano, pak vlákno, které požadavek podává, se uvede do stavu čekání a o požadavku se vytvoří záznam. Protože je třeba vytvářet záznamy, je objektem kritické sekce datová struktura.

Když funkce Opusťte kritickou sekci volané vláknem, které má aktuálně oprávnění ke spuštění své kritické části kódu spojeného s daným objektem kritické sekce, může systém zkontrolovat, zda ve frontě není další vlákno, které čeká na uvolnění tohoto objektu. Systém pak může odstranit čekající vlákno ze stavu čekání a bude pokračovat ve své práci (v časových řezech, které jsou mu přiděleny).

Po dokončení práce se záznamem TRTLCriticalSection jej musíte uvolnit voláním procedury DeleteCriticalSection(), která je definována takto:

procedure DeleteCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Někdy je to nutné při práci s více vlákny nebo procesy synchronizovat provádění dva nebo více z nich. Důvodem je nejčastěji to, že dvě nebo více vláken mohou vyžadovat přístup ke sdílenému prostředku opravdu nelze poskytnout více vláknům najednou. Sdílený prostředek je prostředek, ke kterému lze přistupovat současně několika spuštěnými úlohami.

Je volán mechanismus, který zajišťuje proces synchronizace omezení přístupu. Potřeba vyvstává také v případech, kdy jedno vlákno čeká na událost generovanou jiným vláknem. Přirozeně musí existovat nějaký způsob, kterým bude první vlákno pozastaveno, než dojde k události. Poté by vlákno mělo pokračovat ve svém provádění.

Úloha může být ve dvou obecných stavech. Za prvé, úkol může být proveden(nebo buďte připraveni ke spuštění, jakmile získá přístup ke zdrojům CPU). Za druhé, úkol může být blokováno. V tomto případě je jeho provádění pozastaveno, dokud není uvolněn zdroj, který potřebuje, nebo nastane určitá událost.

Windows má speciální služby, které umožňují určitým způsobem omezit přístup ke sdíleným prostředkům, protože bez pomoci operačního systému nemůže samostatný proces nebo vlákno sám určit, zda má výhradní přístup ke zdroji. Operační systém Windows obsahuje proceduru, která během jedné souvislé operace zkontroluje a pokud možno nastaví příznak přístupu ke zdroji. V jazyce vývojářů operačního systému se tato operace nazývá kontrola a montáž. Volají se příznaky používané k zajištění synchronizace a řízení přístupu ke zdrojům semafory(semafor). Win32 API poskytuje podporu pro semafory a další synchronizační objekty. Knihovna MFC také zahrnuje podporu pro tyto objekty.

Synchronizační objekty a třídy mfc

Rozhraní Win32 podporuje čtyři typy synchronizačních objektů – všechny jsou nějakým způsobem založeny na konceptu semaforu.

Prvním typem objektu je samotný semafor, popř klasický (standardní) semafor. Umožňuje omezenému počtu procesů a vláken přístup k jedinému prostředku. V tomto případě je přístup ke zdroji buď zcela omezen (jeden a pouze jeden podproces nebo proces může přistupovat ke zdroji v určitém časovém období), nebo pouze malý počet vláken a procesů získá současný přístup. Semafory jsou implementovány pomocí čítače, jehož hodnota se snižuje, když je semafor přiřazen k úloze, a zvyšuje se, když úloha uvolňuje semafor.

Druhým typem synchronizačních objektů je exkluzivní (mutexový) semafor. Je navržen tak, aby zcela omezil přístup ke zdroji, takže k prostředku může v daný okamžik přistupovat pouze jeden proces nebo vlákno. Ve skutečnosti se jedná o speciální typ semaforu.

Třetím typem synchronizačních objektů je událost nebo objekt události. Používá se k blokování přístupu ke zdroji, dokud nějaký jiný proces nebo vlákno nedeklaruje, že zdroj lze použít. Tento objekt tedy signalizuje dokončení požadované události.

Pomocí synchronizačního objektu čtvrtého typu můžete zakázat provádění určitých částí programového kódu několika vlákny současně. K tomu musí být tyto oblasti deklarovány jako kritický úsek. Když jedno vlákno vstoupí do této sekce, ostatní vlákna nebudou dělat totéž, dokud první vlákno sekci neopustí.

Kritické sekce se na rozdíl od jiných typů synchronizačních objektů používají pouze k synchronizaci vláken v rámci stejného procesu. Jiné typy objektů lze použít k synchronizaci vláken v rámci procesu nebo k synchronizaci procesů.

V MFC je synchronizační mechanismus poskytovaný rozhraním Win32 podporován následujícími třídami, které jsou odvozeny od třídy CSyncObject:

    CCriticalSection- implementuje kritickou sekci.

    CEvent- implementuje objekt události

    CMutex- implementuje exkluzivní semafor.

    CSemafor- implementuje klasický semafor.

Kromě těchto tříd MFC také definuje dvě pomocné synchronizační třídy: CSingleLock A CMultiLock. Řídí přístup k objektu synchronizace a obsahují metody používané k poskytování a uvolňování takových objektů. Třída CSingleLockřídí přístup k jedinému synchronizačnímu objektu a třídě CMultiLock- na několik objektů. V následujícím budeme uvažovat pouze o třídě CSingleLock.

Jakmile je vytvořen jakýkoli synchronizační objekt, přístup k němu lze řídit pomocí třídy CSingleLock. Chcete-li to provést, musíte nejprve vytvořit objekt typu CSingleLock pomocí konstruktoru:

CSingleLock(CSyncObject* pObject, BOOL bInitialLock = FALSE);

První parametr předává ukazatel na synchronizační objekt, například semafor. Hodnota druhého parametru určuje, zda se má konstruktor pokusit o přístup k tomuto objektu. Pokud je tento parametr nenulový, pak bude získán přístup, jinak nebudou provedeny žádné pokusy o získání přístupu. Pokud je udělen přístup, pak vlákno, které vytvořilo objekt třídy CSingleLock, bude zastavena, dokud nebude odpovídající synchronizační objekt metodou uvolněn Odemknout třída CSingleLock.

Když je vytvořen objekt typu CSingleLock, lze přístup k objektu, na který ukazuje pObject, ovládat pomocí dvou funkcí: Zámek A Odemknout třída CSingleLock.

Metoda Zámek je určen k získání přístupu k objektu k objektu synchronizace. Vlákno, které jej volalo, je pozastaveno, dokud se metoda nedokončí, to znamená, dokud není získán přístup k prostředku. Hodnota parametru určuje, jak dlouho bude funkce čekat na přístup k požadovanému objektu. Pokaždé, když se metoda úspěšně dokončí, hodnota čítače přidruženého k objektu synchronizace se sníží o jednu.

Metoda Odemknout Uvolní objekt synchronizace a umožní ostatním vláknům používat prostředek. V první verzi metody se hodnota čítače spojeného s tímto objektem zvýší o jednu. Ve druhé možnosti první parametr určuje, o kolik se má tato hodnota zvýšit. Druhý parametr ukazuje na proměnnou, do které bude zapsána předchozí hodnota čítače.

Při práci s třídou CSingleLock Obecný postup pro řízení přístupu ke zdroji je:

    vytvořit objekt typu CSyncObj (například semafor), který bude použit k řízení přístupu ke zdroji;

    pomocí vytvořeného synchronizačního objektu vytvořte objekt typu CSingleLock;

    pro získání přístupu ke zdroji zavolejte metodu Lock;

    přístup ke zdroji;

    Voláním metody Unlock uvolněte prostředek.

Následující text popisuje, jak vytvářet a používat semafory a objekty událostí. Jakmile porozumíte těmto konceptům, můžete se snadno naučit a používat dva další typy synchronizačních objektů: kritické sekce a mutexy.

Ahoj! Dnes budeme nadále zvažovat vlastnosti vícevláknového programování a mluvit o synchronizaci vláken.

Co je to „synchronizace“? Mimo oblast programování se to týká nějakého druhu nastavení, které umožňuje dvěma zařízením nebo programům spolupracovat. Například smartphone a počítač lze synchronizovat s účtem Google a osobní účet na webových stránkách lze synchronizovat s účty na sociálních sítích, abyste se pomocí nich mohli přihlásit. Synchronizace vláken má podobný význam: nastavuje způsob vzájemné interakce vláken. V předchozích přednáškách naše vlákna žila a pracovala odděleně od sebe. Jeden něco počítal, druhý spal, třetí něco zobrazoval na konzoli, ale vzájemně se neovlivňovaly. Ve skutečných programech jsou takové situace vzácné. Několik vláken může aktivně pracovat například se stejnou sadou dat a něco v ní změnit. To vytváří problémy. Představte si, že více vláken píše text na stejné místo – například textový soubor nebo konzola. Tento soubor nebo konzola se v tomto případě stane sdíleným prostředkem. Vlákna o své existenci navzájem nevědí, a tak si jednoduše zapisují vše, co mohou zvládnout v čase, který jim plánovač vláken přidělí. V nedávné přednášce kurzu jsme měli příklad, k čemu by to vedlo, připomeňme si to: Důvod spočívá ve skutečnosti, že vlákna pracovala se sdíleným zdrojem, konzolí, bez vzájemné koordinace akcí. Pokud plánovač vláken přidělil čas vláknu-1, okamžitě vše zapíše do konzole. Není důležité, co jiná vlákna již stihla napsat nebo neměla čas napsat. Výsledek, jak vidíte, je katastrofální. Proto byl ve vícevláknovém programování zaveden speciální koncept mutex (z anglického „mutex“, „vzájemné vyloučení“ - „vzájemné vyloučení“). Mutex úkol- poskytnout mechanismus, aby pouze jedno vlákno mělo v určitou dobu přístup k objektu. Pokud vlákno-1 získalo mutex objektu A, ostatní vlákna k němu nebudou mít přístup, aby v něm něco změnili. Dokud nebude mutex objektu A uvolněn, budou zbývající vlákna nucena čekat. Příklad ze života: představte si, že se vy a 10 dalších cizích lidí účastníte školení. Musíte se střídat ve vyjadřování myšlenek a o něčem diskutovat. Ale protože se vidíte poprvé, abyste se neustále navzájem nepřerušovali a nesklouzli do šumu, použijete pravidlo „mluvící koule“: mluvit může pouze jedna osoba – ten, kdo má míč v ruce. jeho ruce. Tímto způsobem se diskuse ukazuje jako adekvátní a plodná. Mutex je tedy v podstatě taková koule. Pokud je mutex objektu v rukou jednoho vlákna, ostatní vlákna nebudou mít k objektu přístup. Pro vytvoření mutexu nemusíte nic dělat: je již zabudován do třídy Object, což znamená, že každý objekt v Javě ho má.

Jak funguje synchronizovaný operátor

Pojďme se seznámit s novým klíčovým slovem - synchronizované. Označuje určitou část našeho kódu. Pokud je blok kódu označen klíčovým slovem synchronized, znamená to, že blok může být spuštěn pouze jedním vláknem najednou. Synchronizaci lze realizovat různými způsoby. Vytvořte například celou synchronizovanou metodu: public synchronized void doSomething() ( //...logika metody) Nebo napište blok kódu, kde se synchronizace provádí na nějakém objektu: public class Main ( private Object obj = new Object () ; public void doSomething () ( synchronized (obj) ( ) ) ) Význam je jednoduchý. Pokud jedno vlákno zadá blok kódu, který je označen slovem synchronizovaný, okamžitě získá mutex objektu a všechna ostatní vlákna, která se pokoušejí vstoupit do stejného bloku nebo metody, jsou nucena čekat, dokud předchozí vlákno nedokončí svou práci a uvolní monitor. Mimochodem! V přednáškách kurzu jste již viděli příklady synchronizovaných , ale vypadaly jinak: public void swap () ( synchronizované (toto) ( //...logika metody) ) Téma je pro vás nové a samozřejmě zpočátku dojde k záměně se syntaxí. Proto si hned pamatujte, abyste se později v metodách psaní nepletli. Tyto dva způsoby zápisu znamenají totéž: public void swap () ( synchronizované (toto) ( //...logika metody) ) public synchronized void swap () ( ) ) V prvním případě vytvoříte synchronizovaný blok kódu ihned po zadání metody. Je synchronizován objektem this, tedy aktuálním objektem. A ve druhém příkladu vložíte slovo synchronizovaný na celou metodu. Již není potřeba explicitně označovat jakýkoli objekt, na kterém se synchronizace provádí. Jakmile je celá metoda označena slovem, bude tato metoda automaticky synchronizována pro všechny objekty třídy. Nepouštějme se do diskuze, která metoda je lepší. Prozatím si vyberte, co se vám nejvíce líbí :) Hlavní věc je zapamatovat si: metodu můžete prohlásit za synchronizovanou pouze tehdy, když je veškerá logika v ní prováděna jedním vláknem současně. V tomto případě by bylo například chybou synchronizovat metodu doSomething(): public class Main ( private Object obj = new Object () ; public void doSomething () ( //...nějaká logika dostupná všem vláknům synchronizováno (obj) ( //logika, která je v daný okamžik dostupná pouze pro jedno vlákno) ) ) Jak vidíte, část metody obsahuje logiku, pro kterou není vyžadována synchronizace. Kód v něm může být spuštěn několika vlákny současně a všechna kritická místa jsou přidělena samostatnému synchronizovanému bloku. A jeden moment. Podívejme se pod drobnohledem na náš příklad záměny jmen z přednášky: public void swap () ( synchronizované (toto) ( //...logika metody } } Dávej pozor: synchronizace se provádí pomocí tohoto. Tedy na konkrétním objektu MyClass. Představte si, že máme 2 vlákna (Thread-1 a Thread-2) a pouze jeden objekt MyClass myClass . V tomto případě, pokud vlákno-1 zavolá metodu myClass.swap(), mutex objektu bude zaneprázdněn a vlákno-2 při pokusu o volání myClass.swap() přestane čekat, až se mutex uvolní. Pokud máme 2 vlákna a 2 objekty MyClass - myClass1 a myClass2 - na různých objektech mohou naše vlákna snadno současně provádět synchronizované metody. První vlákno se spustí: myClass1. swap(); Druhý ano: myClass2. swap(); V tomto případě klíčové slovo synchronized uvnitř metody swap() neovlivní činnost programu, protože synchronizace se provádí na konkrétním objektu. A ve druhém případě máme 2 objekty.Vlákna si proto vzájemně nedělají problémy. Po všem dva objekty mají 2 různé mutexy a jejich získávání je na sobě nezávislé.

Vlastnosti synchronizace ve statických metodách

Co dělat, pokud potřebujete synchronizovat statická metoda? class MyClass ( private static String name1 = "Olya" ; private static String name2 = "Lena" ; public static synchronized void swap () (String s = name1; name1 = name2; name2 = s; ) ) Není jasné, co bude plní v tomto případě roli mutex. Koneckonců, už jsme se rozhodli, že každý objekt má mutex. Problém je ale v tom, že k volání statické metody MyClass.swap() nepotřebujeme objekty: metoda je statická! Takže, co bude dál? :/ Vlastně s tím není problém. Tvůrci Javy se o všechno postarali :) Pokud je metoda, která obsahuje kritickou „vícevláknovou“ logiku, statická, synchronizace bude provedena podle třídy. Pro větší přehlednost lze výše uvedený kód přepsat jako: class MyClass ( private static String name1 = "Olya" ; private static String name2 = "Lena" ; public static void swap () (synchronizované (MyClass. class ) ( String s = jméno1 ; jméno1 = jméno2; jméno2 = s; ) ) ) V zásadě jste si to mohli vymyslet sami: protože neexistují žádné objekty, musí být synchronizační mechanismus nějak „napevno zapojen“ do samotných tříd. Je to tak: můžete také synchronizovat napříč třídami.