Multitasking & Multithreading

Úvod

Multitasking (nebo také "multi-tasking", "multi-processing", "multiprogramming", "concurrency") je technika používaná operačními systémy pro využívání jednoho procesoru více nezávislými procesy. To přináší jak některé výhody, tak samozřejmě i jisté nevýhody. První multitaskingové operační systémy byly vytvořeny začátkem 60. let. Byly to OS na velkých sálových počítačích. Na počítačích typu PC se začali používat poměrně nedávno.

Realizace multitaskingu

Většina dnešních počítačů je osazena pouze jedním procesorem. Procesy samozřejmě nemohou na jednom procesoru být zpracovávány společně. OS tedy musí zajistit, aby procesor zpracovávání jednotlivých procesů střídal v krátkých časových intervalech, což se uživateli jeví jako současné zpracovávání úloh.
Existují tří hlavní způsoby jak toho dosáhnout:
a) task switching
b) cooperative multitasking
c) pre-emptive multitasking

Task switching

- přepínání programů
OS s přepínáním programů je přímým předchůdcem kooperativního multitaskingu. Vznikl na interaktivních systémech na základě poměrně jednoduché úvahy: nejzřetelnější výhodou multitaskingu pro uživatele je možnost přejít z jedné aplikace k druhé, bez nutnosti ukončení práce. Přepínání programů je dosaženo tzv. vzájemným voláním, což není vlastnost přímo OS, ale jednotlivých programů. Ty mohou umožnit spuštění jiného programu a práci s ním, po ukončení tohoto programu se obnoví stav původního programu. Vzájemné volání bylo rozšířeno zejména u programů pro Apple Macintosh a dnes se s ním setkáme i u programů pod MS-DOS (tam se ale obvykle nevolá přímo program, ale příkazový interpret). Existují dvě skupiny OS s přepínáním programů: Pokud přecházíme od jednoho programu k druhému, chceme vlastně tento program přerušit takovým způsobem, abychom mohli jeho běh po nějakém čase obnovit. Musíme tedy důležité informace uložit někam na vnější paměť. To by znamenalo obrovské množství informací - obsah celé operační paměti, obsah registrů procesoru, nastavení různých zařízení, stav pomocných procesorů (blitter, kanály) atd. Takové řešení by bylo značně neefektivní a v některých případech i nemožné. Obsah operační paměti se neukládá, ale pouze se zajistí, aby část, kterou daný proces využívá, nemohla být použita jiným procesem. Málokterý program využívá všechna zařízení, bylo by proto naprosto zbytečné, starat se o všechny. Program může být také požádán, aby se dostal před přerušením do předem definovaného stavu: ukončil práci s periferními zařízeními i s obrazovkou (kanály, blitter), ukončil výpočty v pohyblivé řadové čárce (koprocesor) atp. Dále můžeme program přimět, aby po obnovení práce nespoléhal na obsah registrů procesoru. Zbude pak jen naprosté minimu údajů, které musíme uložit - pouze nastavení zásobníku a obsah obrazovky. Tyto údaje jsou uloženy pro všechny momentálně rozpracované úlohy v tabulce, která se nazývá kontext. Programy, které mají v paměti svůj kontext se nazývají procesy (task).
Pokud je třeba přepnout z aktivního procesu na jiný proces, například na základě nějakého signálu uživatele, OS vyčká až proces zavolá službu, kterou oznamuje, že je v "přerušitelném" stavu (to je každý proces povinen pravidelně dělat, a OS mají tuto službu obvykle kombinovanou s jinými službami OS (často např. přerušení od klávesnice), aby ji program nemohl obejít). Pak OS uloží do tabulky na místo odpovídající dosud aktivnímu procesu adresu vršku jeho zásobníku (přečte ji z patřičného registru procesoru). Nakonec systém zjistí z tabulky adresu zásobníku nového procesu a zavede ji do patřičného registru procesoru. Ukončí službu a od té chvíle běží nový proces - došlo k přepnutí kontextu (context switch) - byl odebrán procesor starému procesu a přidělen procesu novému.
Podrobněji: Těsně před přepnutím kontextu obsahuje zásobník procesu číslo 1 (aktivní) na vrcholku návratovou adresu pro návrat ze systémové služby zpět do kódu procesu. Dříve, než dojde k návratu, však přepnutí kontextu změní zásobník. Ve chvíli návratu je tedy aktivní zásobník procesu číslo2, na jehož vrcholu je samozřejmě také návratová adresa, ale tentokrát do procesu číslo 2 - a tam se systémová služba vrátí.
Při vytváření procesu je vytvořen tomuto procesu zásobník, na který je uložena adresa jeho první instrukce a adresa tohoto zásobníku je uložena do kontextu.

Cooperative multitasking

- kooperativní multitasking
Jeho princip vychází přímo z mechanizmu přepínání programů. Tam mohlo dojít k přepnutí kontextu pouze na žádost uživatele. To vedlo k tomu že, značné množství procesorového času bylo nevyužito. To si tvůrci kooperativního multitaskingu uvědomovali a byli vedeni snahou tento nevyužitý čas přidělit jinému procesu. V případě, že aktivní proces například čeká na stisk klávesy, může být spuštěn proces jiný. Poté co tento proces zavolá přerušovací službu vrátí se procesor ke zpracovávání původního procesu. Pokud ten stále nedostal signál od klávesnice bude se situace opakovat. Proces čekající, v tomto případě na stisk klávesnice, se nazývá proces na popředí (foreground), procesy na pozadí (background) jsou pak ty, které využívají procesorový čas, který by jinak byl promarněn. Takovýto multitasking byl použit např. u Windows 3.x.
Kooperativní multitasking má všechny výhody, které poskytuje systém s neomezeným přepínáním programů. Navíc lépe využívá procesor.
Vyznačuje se ale některými závažnými nevýhodami:

Pre-emptive multitasking

- preemptivní multitasking
Hlavní nevýhodou předchozího řešení je tedy to, že každý proces musí s OS spolupracovat. Toto omezení odstraňuje právě preemptivní multitasking. Ten umožňuje přerušení kteréhokoli procesu na kterémkoli místě, aniž by proces sám do toho musel nějakým způsobem zasahovat. U kooperativního multitaskingu proces zajišťuje svoji nezávislost při svém opětovném spuštění na registrech procesoru, koprocesoru, stavu blitteru, I/O procesorů,... Není ovšem možné toto všechno uložit do kontextu, je nutné to nějakým rozumným způsobem obejít. Do kontextu se uloží pouze stav procesoru a případně koprocesoru a s ostatními prvky systému je povoleno pracovat jen jedinému procesu. Jejich stav v případě přerušení procesu se tedy nemusí ukládat, protože jiný proces s nimi nemůže pracovat. S tím se ovšem objevuje nebezpečí zablokování procesů. Proto moderní OS zakazují používání systémových prostředků všem procesům kromě speciálně vyhrazených systémových procesů. Takový proces se nazývá server a udržuje si informace o svých klientech, kterým poskytuje služby umožňující jim nepřímo využívat dané zařízení.
Zpracovávání procesů se velmi rychle střídá (řádově desítky až stovky milisekund) a tak vzniká dojem paralelního zpracování. Velikost intervalu, po jehož uplynutí dochází ke střídání procesů se nazývá časové kvantum (time-slice). Po vypršení časového kvanta tedy dojde k zastavení jednoho procesu a spuštění dalšího. Střídání úloh je obvykle realizováno pomocí vnějšího přerušení od čítače, který čítá hodinové pulsy a po uplynutí předem definované doby, tzv. základního časového intervalu - tiku velikosti jednotek až desítek milisekund, generuje žádost o přerušení. OS při každém tiku provede některé nutné servisní úkoly (např. aktualizace systémového času) a pokud zjistí, že procesu vypršelo časové kvantum, přeruší jeho činnost. To se nazývá preempcí.
Preemptivní multitasking umožňuje nastavení priorit jednotlivých procesů.
Přidělování procesoru
Přidělování procesoru procesům provádí dva moduly OS: plánovač (scheduler) a dispečer (dispatcher). Scheduler z fronty čekajících procesů ve swapovací oblasti vybírá procesy, které budou nahrány do paměti a tak připraveny ke spuštění dispečerem. Dispečer pak vybírá podle určitých kriterií z fronty procesů, které jsou připraveny ke spuštění ten, který bude opravdu spuštěn.

Uzavírání zdrojů
Za jistých okolností potřebují procesy k systémovým prostředkům výlučný přístup. Pokud proces získal výlučný přístup říkáme, že zdroj uzavřel. To je nutné, aby nedošlo k časové závislosti procesů. K časové závislosti procesu s jinými procesy může dojít jen tehdy, když proces vykonává určitou část svého kódu, kterou nazýváme kritickou oblastí. Musíme tedy zajistit, aby v určitý časový okamžik byl v kritické oblasti pouze jeden z procesů. Této podmínce se říká podmínka vzájemného vyloučení (mutual exclusion). Vzájemné vyloučení se realizuje tak, že první proces, který chce se zdrojem manipulovat, zdroj uzavře. Po uzavření zdroje tento proces vstupuje do kritické oblasti a ostatní procesy musí počkat, dokud tento proces kritickou oblast neopustí. Jakmile proces kritickou oblast opustí, do kritické oblasti může vstoupit další proces. Říkáme také, že proces zdroj otevřel (uvolnil).
Uzavírání zdrojů tedy není nic jiného než realizace podmínky vzájemného vyloučení procesů.

Realizace vzájemného vyloučení
Vzájemné vyloučení lze realizovat nejrůznějšími způsoby. Ty se nejčastěji dělí do dvou kategorií a to z hlediska stavu, ve kterém se nacházejí procesy čekající na vstup do kritické oblasti. Ty jsou buď ve stavu aktivního čekání nebo ve stavu zablokování.
Zatímco procesy ve stavu aktivního čekání neustále testují splnění určité podmínky, aby mohly vstoupit do kritické oblasti, a tím spotřebovávají procesorový čas (jsou zařazovány dispatcherem ke zpracování), tak při řešení, při kterém jsou procesy ve stavu zablokování je jednoduše proces, který se neúspěšně pokusil vstoupit do kritické oblasti, zablokován a tudíž není dispatcherem nadále zařazován ke zpracování. Poté co jiný proces opustí danou kritickou oblast je tento proces probuzen, dispatcher jej časem zařadí ke zpracování a proces může do kritické oblasti vstoupit. Nedochází tedy ke zbytečnému spotřebovávání procesorového času.
Oba dva způsoby se mohou řešit pomocí zámku nebo semaforu.

Vzájemné vyloučení realizované zámkem
Zámek je procesy sdílená proměnná. Procesy ji mohou nastavovat na různé hodnoty. Pokud ji některý z procesů nastaví na nenulovou hodnotu, oznamuje tím ostatním procesům to, že vstoupil do kritické oblasti. Pokud je hodnota zámku nulová, znamená to, že se žádný z procesů v kritické oblasti nenachází. Kdyby ovšem tento systém pracoval následovně mohlo by se stát, že do kritické oblasti vstoupí i více procesů: 1. Na počátku bude zámek = 0.
2. Proces testuje hodnotu zámku. Je-li jeho hodnota nulová pokračuje bodem 3. Není-li tomu tak provádí opakované testování hodnoty zámku dokud se hodnota zámku nezmění na 0 (do kritické oblasti nevstoupí).
3. Nastaví zámek na 1 a vstoupí do kritické oblasti. .
4. Těsně před výstupem z kritické oblasti nastaví zámek = 0

Pokud by ovšem byl proces A mezi body 2. a 3. přerušen preempcí a jiný proces B se pokusil vstoupit do této kritické oblasti a nevystoupil by z ní do dalšího volání procesu A vstoupily by do kritické oblasti oba dva procesy. Tomu je nutné nějakým způsobem zabránit. Jsou používané tyto tři možnosti:

Někdy se technika zámku pozměňuje tak, že proces po zjištění, že zámek je uzavřen, požádá operační systém o zablokování na určitou dobu a opětovný test zámku provede až po svém probuzení. To sice omezuje plýtvání procesorovým časem, ale na druhé straně může podstatně prodloužit dobu čekání procesu (neboť uvolněný zdroj si může mezitím uzavřít jiný proces) .
Vzájemné vyloučení realizované semaforem
Toto řešení je vhodnější. Semafor je proměnná nabývající hodnoty 0,1,2,......, n. Semafory musí být implementovány operačním systémem jako služba systému.
Pomocí semaforu lze implementovat vzájemné vyloučení následujícím způsobem:
1. Na počátku je semafor (s) nastaven např. na hodnotu 1 (s=1)
2. Před vstupem do kritické oblasti je testována hodnota s. Pokud je větší jak 0 (s != 0) sníží se o 1 (s = s - 1). Pokud je s = 0 je proces pozastaven. Po svém probuzení a opětovném spuštění dokončí operaci, tj. sníží hodnotu s o 1 (s = s - 1)
3. Před opuštěním kritické oblasti proces zvýší s o 1 (s = s + 1). Jeden z pozastavených procesů je probuzen

Uváznutí procesů - deadlock
V multitaskingových operačních systémech může při uzavírání zdrojů snadno dojít k tak zvanému uváznutí procesů. Dojde k tomu jestliže dva či více procesů na sebe navzájem čekají. (viz. obr.)

Deadlock
Obr.deadlock
Proces A si uzavřel Zdroj I. a Proces B si uzavřel Zdroj II. Poté se snaží Proces A uzavřít Zdroj II., ale ten je uzavřen Procesem B. OS proces A tedy zablokuje. Proces B se snaží uzavřít Zdroj I. a je OS také zablokován.
Řešením může být tzv. úplné vyhrazení prostředků tzn., že při spuštění procesu budou zablokovány veškeré zdroje, které by mohl tento proces během své činnosti potřebovat. Je zřejmé že, takové řešení je značně neefektivní a vede k snížení průchodnosti systému. Další možností je, že v případě, kdy je zdroj, který proces potřebuje, uzavřen už jiným procesem, tak proces uvolní všechny ostatní zdroje, které si pro sebe uzavřel a požádá OS o pozastavení. Po určité době je proces opět probuzen a pokusí se uzavřít si pro sebe zdroje, které potřebuje pro předepsanou činnost. Při takovéto strategii nemůže nikdy dojít k uváznutí procesů, ale procesy se mou dostat do stavu, který se nazývá starvation - stárnutí procesů (viz. níže).
Jiným řešením je, že na množině zdrojů {Rn}je definováno uspořádání R1této strategii nemůže dojít k uváznutí procesů, procesy se však mohou dostat do stavu, který se nazývá stárnutí procesů.

Stárnutí procesů - starvation
Procesy sice nečekají na otevření určitého zdroje, jsou v omezené míře stále aktivní, ale nepodaří se jim uzavřít si všechny potřebné zdroje. Předpokládejme, že jsou dva procesy a dva zdroje. Oba procesy pro úspěšné dokončení své činnosti potřebuje vždy dva zdroje. Oba dva si tedy uzavřou vždy jeden zdroj. Po určité době se procesy pokusí uzavřít druhý zdroj. Ten je ale již vyhrazen jinému procesu a procesy tedy uvolní již uzavřené zdroje a požádají OS o přerušení. Po jejich opětovném spuštění se celá situace může opakovat.

Starvation
Obr. Starvation
Vyhnout se tomu dá např. tak, že doba, po kterou budou jednotlivé procesy pozastaveny , bude různě dlouhá.

Multithreading

Je to schopnost programu sám sebe větvit. Program se dělí na tzv. vlákna (threads of executions), která se zdají běžet současně. To má výhodu v tom, že při provádění nějaké náročnější a dlouhotrvající práce (např. ukládání dlouhého souboru) se aplikace "nezasekne". Programátor zpravidla vytvoří jedno vlákno frontové, které vyrobí všechna okna, jedno nebo více vláken bezfrontových, která počítají. Je známé tzv. pravidlo 1/10 sekundy "Cokoli trvá déle než 1/10 sekundy, mělo by se provádět v novém bezfrontovém vláknu."
Zatímco u multitaskingu jsou jednotlivé procesy zcela oddělené (vlastní paměť,...) při multithreadingu všechna vlákna (jedné aplikace) sdílejí stejné systémové zdroje - paměť, soubory, globální proměnné, každé vlákno má svůj zásobník, tj. automatické proměnné jsou pro každé vlákno zvlášť.
Ve Windows 3.x každá běžící aplikace je jedna úloha. OS přiděluje CPU těmto úlohám používáním kooperativního multitaskingu a spoléhá na aplikace, že umožní předání procesorového času jiné úloze. Ve Win32 se už nemluví o úlohách, ale o procesech a vláknech. Proces se může skládat z jednoho či více vláken. Proces a vlákna jsou přerušovány preempcí. Ve Windows 3.x byla každá úloha pouze jedním vláknem. Program byl prováděn od začátku do konce. Ve Win32 proces vzniká jako jedno vlákno, které může vytvořit vlákna další běžící na pozadí (formátování diskety, provádění složitého výpočtu, vyhledávání,....). Těmto vláknům je procesorový čas předáván zcela nezávisle, jako samostatným programům.

Thread Scheduling
Rozhodnutí kterému vláknu přidělit procesorový čas a na jak dlouho je pro OS složitý proces. V prvé řadě bere Scheduler ohled na priority jednotlivých vláken. Ve Windows 95 a NT je možno přidělit každému vláknu hodnotu priority od 0 do 31, čím vyšší číslo, tím vyšší priorita. Hodnota priority 31 je reservována pro extrémně krizová vlákna (např. získávání dat v reálném čase) zatímco hodnota 0 je používána OS pro téměř zbytečná vlákna, která se spouštějí pouze v případě, že je procesorový čas nevyužíván. Nejpoužívanější jsou hodnoty od 7 do 11.
Po několika milisekundách scheduler vyhodnotí situaci a předá procesorový čas vláknu s nejvyšší prioritou. Pokud jsou dvě či více vláken se stejnou prioritou je spuštěno to od jehož posledního provádění uběhlo nejvíce času. Vlákna s vyšší prioritou jsou tedy prováděny před vlákny s nižší prioritou a vlákna s nízkou prioritou nikdy nepřeruší vlákna s vysokou prioritou. Neznamená to ale, že vlákno s nižší prioritou nebude nikdy spuštěno. Jsou-li dvě vlákna A a B s prioritou 10 a 9 bude prováděno vlákno A. Pokud má vlákno A prázdnou frontu zpráv tzn., že dokud nepřijde další zpráva nemá nic na práci, je toto vlákno pozastaveno a může být spuštěno vlákno B. Pozastavenému vláknu není přidělován procesorový čas dokud neobdrží nějakou zprávu, která ho probudí a přeruší preempcí vlákno B, jež bylo doposud prováděno. Mnoho vláken tak stráví značné množství času ve stavu čekání na nějakou vstupní událost.

index

William Wollis 1999