Lekce 1 - Úvod do vícevláknových aplikací v C# .NET

Vydávání, hosting a aktualizace umožňují jeho sponzoři.
Vítejte u první lekce kurzu o programování vícevláknových aplikací
neboli multithreadingu v C# .NET. Naučíme se zde plně využívat moderní
vícejádrové procesory a také spouštět na pozadí úlohy, které by jinak
zasekly hlavní vlákno aplikace. Dostaneme se i k nejnovějším technologiím
z .NET frameworku jako jsou paralelní programování, úlohy nebo klíčová
slova async
a await
.
Úvod do teorie vláken
Než začneme používat vlákna, popišme si jakým způsobem spouští aplikace náš operační systém. Jelikož jsme u C#, tak můžeme hovořit o Windows, avšak princip platí pro většinu ostatních. Windows je víceúlohový operační systém (multitasking). To znamená, že dokáže spustit více aplikací najednou. Přitom jádro procesoru dokáže obvykle spustit jen jednu instrukci v jeden čas. Jistě víte, že Windows dokázal spouštět více aplikací najednou dlouho předtím, než byly na trhu vícejádrové procesory a i teď si můžete spustit kolik aplikací chcete a to bez ohledu na to, kolikajádrový procesor vlastníte. Jak je to možné?
Windows mezi spuštěnými aplikacemi jednoduše rychle přepíná (přesněji to dělá tzv. scheduler a o přepínání hovoříme jako o timeslicingu). Systém jednu aplikaci zastaví a spustí na okamžik aplikaci jinou. Uživatel to vnímá jako by aplikace běžely najednou, i když tomu tak ve skutečnosti není. S příchodem vícejádrových procesorů se princip nezměnil, Windows stále přepíná mezi aplikacemi, ovšem dokáže vykonávat několik instrukcí najednou na každém procesorovém jádru.
Aplikace, proces a vlákno
Zamysleme se nad termíny aplikace, proces a vlákno. Aplikaci si jistě dokážeme představit, je to např. internetový prohlížeč, ve kterém čtete tento článek. Co je ovšem proces a co vlákno?
Proces
Proces je instance běžící aplikace. Pokud si spustíme 3x kalkulačku,
nalezneme ve správci úloh 3x proces calc.exe
. Některé
složitější aplikace mohou využívat několik procesů, např. Google Chrome
vytvoří proces pro každou otevřenou záložku. Většinou mají aplikace
však jen jeden proces.

Vlákno
V procesu může běžet několik jeho vláken. Každá aplikace má minimálně jeden proces, ve kterém běží její hlavní vlákno, případně nějaká další vlákna. Zatím, co hlavní vlákno máme připravené, další vlákna si vytváříme my sami. Operační systém spustí naše vlákno na nějakém jádru a potom ho rychle uspává a probouzí jak se mu to zrovna hodí. Ve výsledku nám vlákna v procesu běží paralelně a můžeme např. provádět nějaké složité analýzy, aniž bychom zasekli hlavní vlákno aplikace a to dokonce i v několika vláknech najednou, přičemž bude samotný výpočet několikrát rychlejší.
Synchronizace
Vše zní skvěle, že? Vlákna se ovšem v praxi používají opravdu jen pokud je nutně potřebujeme. Je s nimi totiž spojený jeden obrovský problém a tím je synchronizace. Nikdy nevíme, kdy budou naše vlákna uspána, systém to provede bez ohledu na to, co dané vlákno právě dělá. Asi si dokážeme představit, že se metoda vlákna zasekne na nějakém řádku zdrojového kódu. Ve skutečnosti se však může zaseknout i např. v polovině sčítání, protože na 32bitovém procesoru jsou ke sčítání 64bitových čísel zapotřebí 2 instrukce. Pokud tuto hodnotu používáme jiným vláknem, může v ní být nesmysl. Proměnné se také v jádrech cachují, takže se může stát, že má ve stejný čas stejná proměnná několik různých hodnot. K problémům synchronizace se dostaneme během seriálu poměrně podrobně a naučíme se je také řešit.
Vlákna určitě nejsou něco, co by každá aplikace musela nutně obsahovat a ačkoli se inženýři z Microsoftu snaží co mohou, aby práci s nimi co nejvíce zjednodušili (a že se jim to daří), stále technologie ve výsledku aplikaci zkomplikuje. Určitě je používejte s rozvahou.
První vícevláknová aplikace
Naprogramujeme si první vícevláknovou aplikaci. Pro zjednodušení budeme
nějakou dobu pracovat pouze v konzoli. Založte si nový projekt, který
pojmenujte Prepinac
. K projektu přidáme stejnojmennou třídu s
následujícím obsahem:
class Prepinac { public void Vypisuj0() { while (true) { Console.Write("0"); } } public void Vypisuj1() { while (true) { Console.Write("1"); } } public void Prepinej() { Thread vlakno = new Thread(Vypisuj0); vlakno.Start(); Vypisuj1(); } }
První 2 metody třídy jsou velmi jednoduché a simulují nějakou delší činnost. První metoda do nekonečna vypisuje nuly do konzole, druhá metoda stejným způsobem vypisuje jedničky.
Metoda Prepinej()
je pro nás již zajímavější. Sama spustí
výpis jedniček, ale ještě předtím vytvoří nové vlákno, kterému
přiřadí metodu pro výpis nul. Toto vlákno poté také spustí.
Vlákna jsou v .NETu reprezentována třídou Thread. V konstruktoru ji
předáme delegát ThreadStart
, který má následující
podobu:
public delegate void ThreadStart();
Vláknu tedy můžeme předat bezparametrickou metodu typu void
.
V metodě main()
vytvoříme instanci přepínače a necháme ho
přepínat:
static void Main(string[] args) { Prepinac prepinac = new Prepinac(); prepinac.Prepinej(); }
Když aplikaci spustíme, získáme takovýto výstup:

Všimněte si, že jedničky a nuly nejsou úplně na střídačku, jak bychom mohli očekávat. Je vidět, jak vlákno chvíli běží a poté je uspáno. Intervaly se také liší, i když průměrně běží obě vlákna stejně dlouho.
Vlastnosti třídy Thread
Zatím pro nás budou důležité pouze tyto vlastnosti na třídě Thread:
IsAlive
- Označuje, zda metoda vlákna běží.Name
- Každé vlákno si můžeme pojmenovat, což nám usnadní ladění aplikace.
Hlavní vlákno aplikace
K hlavnímu vláknu aplikace se dostaneme pomocí statické vlastnosti Thread.CurrentThread. Bohužel nemá ve výchozím stavu přiřazeno žádné jméno, zkusme mu ho přiřadit a vypsat.
static void Main(string[] args) { Thread.CurrentThread.Name = "Hlavní vlákno"; Console.WriteLine(Thread.CurrentThread.Name); }
V příští lekci, Vlákna v C# .NET - Sleep, Join a lock, se podíváme na uspávání vláken, jejich spojování a základy synchronizace.
Komentáře


Zobrazeno 5 zpráv z 5.