Task Parallel Library a .NET 4.0

S TPL som sa hral už, keď vyšla prvá verzia CTP. TPL je framework, ktorý má uľahčiť vývoj algoritmov optimalizovaný pre viac jadrové procesory. A v dokumentácii som sa dočítal, že CTP nevytvorí viac threadov, ako je jadier v procesoroch. Teda threadov môže byť viac, ale nespiacich by nemalo byť viac. Je to celkom rozumná podmienka, pretože v skutočnosti nemôže bežať paralelne viac threadov ako procesorov. Každý ďalší thread sa musí deliť o procesor, a teda dochádza k prepínaniu threadov, čiže v skutočnosti nebežia paralelne, len sa veľmi rýchlo striedajú.

Než budem pokračovať, tak najprv musím zadefinovať dve veci. Je to čisto moja definícia, ktorú som už párkrát písal na aspnet.sk.

  • Paralelný algoritmus – algoritmus, ktorý využíva viac procesorov. Čiže sa skladá z úloh, ktoré môžu bežať naraz na viacerých procesoroch. Avšak každá takáto úloha využíva len procesor a pamäť. Žiadne iné zdroje ako disk, databázu, web services nepoužíva. Napríklad paralelné triedenie alebo raytracing.
  • Asynchrónna úloha – úloha, ktorá sa má spustiť na pozadí a hlavný proces môže vykonávať niečo iné, pokiaľ sa úloha vykonáva. Typický príklad je asynchrónne volanie web service. Web service sa zavolá na pozadí a hlavný program beží ďalej a nie je zamrznutý.

Tieto definície som si zaviedol po zoznámení sa s TPL CTP1. A taktiež všeobecná poučka hovorila, že TPL je určené práve na paralelné algoritmy a nie asynchrónne úlohy. Paralelné algoritmy využívajú len procesor, a práve preto nemá význam aby sa spúšťalo viac úloh paralelne, ako je procesorov. Proste celý algoritmus nemôže bežať rýchlejšie, ale skôr pomalšie, lebo aj context switch niečo stojí. A na jednojadrovom procesore by mal bežať algoritmus úplne sekvenčne.

Naopak asynchónnych úloh môžem mať spustených oveľa viac ako je procesorov. Veď aj tak každý thread spí a čaká, kým sa niečo dokončí na nejakom inom zariadení (napríklad na servery, kde beží web service).

Práve preto TPL CTP bola určená na paralelné algoritmy, ale nebola vôbec vhodná na asynchrónne úlohy. Lenže toto sa zmenilo v .NET 4.0. Najprv som si všimol, že v MSDN okrem klasických príkladov na paralelné násobenie matíc pribudli aj príklady na asynchrónne volanie web service. A na stránke Introduction to Tasks píšu, že úlohy sú plánované pre maximálnu priepustnosť, ale nič o tom, že by počet threadov bol obmedzený počtom procesorov. Dokonca tam píšu, že celé je to postavené na klasickom .NET ThreadPool, aj keď som niekde čítal, že ten bol prepísaný. História bola asi taká, že v CTP1 to bolo postavené na ThreadPool, potom v CTP2 si napísali vlastný plánovač a asi na základe neho prepísali ThreadPool v .NET 4.0.

Tak som sa rozhodol overiť, ako to je s tým počtom threadov. Spravil som si jednoduchú funkciu, ktorá opakuje cyklus, kým ubehne 15 sekúnd. A túto funkciu som pustil 15-krát paralelne.

private static void WaitTasks()

{

    var time = new TimeSpan(0, 0, 15);

    Parallel.For(0, 15, i => Wait(time, i));

}

 

private static void Wait(TimeSpan time, int id)

{

    var startTime = DateTime.Now;

    Console.WriteLine("Task {0} starts at {1:hh:MM:ss.ff}... {2}", id, startTime, Task.CurrentId);

    var now = DateTime.Now;

    while (now < startTime + time)

    {

        now = DateTime.Now;

    }

    Console.WriteLine("Task {0} ends at {1:hh:MM:ss.ff}... {2}", id, DateTime.Now, Task.CurrentId);

}

A výsledok bol asi nasledovný:

Task 0 starts at 01:10:06.63... 1

Task 7 starts at 01:10:06.69... 2

Task 14 starts at 01:10:07.88... 3

Task 1 starts at 01:10:10.43... 4

Task 8 starts at 01:10:14.31... 5

Task 2 starts at 01:10:18.90... 6

Task 0 ends at 01:10:21.83... 1

Task 3 starts at 01:10:21.83... 1

Task 7 ends at 01:10:22.20... 2

Task 9 starts at 01:10:22.20... 7

Task 14 ends at 01:10:22.90... 3

Task 5 starts at 01:10:22.90... 8

Task 1 ends at 01:10:25.95... 4

Task 11 starts at 01:10:25.95... 9

Task 8 ends at 01:10:29.66... 5

Task 13 starts at 01:10:29.66... 10

Task 2 ends at 01:10:34.47... 6

Task 3 ends at 01:10:36.83... 1

Task 4 starts at 01:10:36.83... 1

Task 9 ends at 01:10:37.30... 7

Task 10 starts at 01:10:37.30... 7

Task 5 ends at 01:10:38.25... 8

Task 6 starts at 01:10:38.25... 8

Task 11 ends at 01:10:40.95... 9

Task 12 starts at 01:10:40.95... 9

Task 13 ends at 01:10:45.11... 10

Task 4 ends at 01:10:51.86... 1

Task 10 ends at 01:10:52.34... 7

Task 6 ends at 01:10:53.26... 8

Task 12 ends at 01:10:55.95... 9

Takže je vidno, že až 5 threadov bežalo paralelné, aj keď nezačali všetky naraz, ale ThreadPool chvíľu čakal, či sa mu nejaký thread neuvoľní. Je to vidieť aj na nasledovnom obrázku.

threads

Potom som ešte spravil to, že som rovnakú funkcionalitu prepísal pomocou Delegate.BeginInvoke, čo využíva ThreadPool a je v .NET už od verzie 1.0.

private static void WaitThreads()

{

    var time = new TimeSpan(0, 0, 15);

    var waitDelegates = new Action<int>[15];

    var waitAsyncResults = new IAsyncResult[waitDelegates.Length];

    for (int i = 0; i < waitDelegates.Length; i++)

    {

        waitDelegates[i] = id => Wait(time, id);

        waitAsyncResults[i] = waitDelegates[i].BeginInvoke(i, null, null);

    }

    WaitHandle.WaitAll(waitAsyncResults.Select(ar => ar.AsyncWaitHandle).ToArray());

}

Výsledok bol ten istý. Tiež sa vytvorilo 5 threadov a aj časy boli rovnaké.

Aby som to celé zhrnul, tak som bol vždy toho názoru (spolu s Vlkom), že s TPL v mojej praxi veľmi do styku neprídem a podobne je na tom so mnou väčšina programátorov. Veď koľkí z vás programujú násobenie matic alebo iné paralelné algoritmy. Zato jednoduché asynchrónne volanie webservice je asi problémom väčšiny z nás. A keďže prístup TPL sa v .NET 4.0 zmenil, tak aj ja musím zmeniť svoj názor. Proste TPL je už vhodná aj na asynchrónne úlohy a stačí si porovnať v mojom príklade funkcie WaitTasks a WaitThreads.

Žiaľ neexistuje na MSDN článok o TaskScheduler, že ako to je s jeho optimalizáciou práve pre paralelné algoritmy. Zatiaľ je tam len To Be Done.

Komentáre

Bez komentárov

Prihlásiť | Registrovať | Pomoc