IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

2. diel - Vlákna v VB.NET - Sleep, Join a lock

V minulom dieli nášho seriálu tutoriálu o viacvláknových aplikáciách v VB .NET, Úvod do viacvláknových aplikácií v VB.NET , sme si vytvorili prvú viacvláknové aplikácie.

V dnešnom dieli sa naučíme vlákna blokovať a zamykať.

Sleep a Join

Aktuálne vlákno môžeme uspať na daný počet milisekúnd a to pomocou statickej metódy Sleep na triede Thread. Vlákno je blokované kým čas nevyprší, potom sa opäť prebúdza a pokračuje vo svojej činnosti.

Vytvorme si nový projekt s triedou Vypisovac, ktorá bude vyzerať podobne, ako Prepinac z minulého dielu:

Public Class Vypisovac
    Public Sub Vypisuj0()
        For index = 1 To 100
            Console.Write("0")
            Thread.Sleep(5)
        Next
    End Sub
    Public Sub Vypisuj1()
        For index = 1 To 150
            Console.Write("1")
            Thread.Sleep(5)
        Next
    End Sub
End Class

Metóda Vypisuj0 () vypíše do konzoly 100 núl a pri každom výpise uspí svoje vlákno na 5ms. Vypisuj1 () vypíše 150 jedničiek a pobeží teda dlhšie (asi o 1/4 sekundy) ako metóda Vypisuj0 ().

Teraz v hlavnej metóde vytvoríme vlákno pre každú metódu a vlákna spustíme. Nakoniec vypíšeme "Hotovo":

Sub Main()
    Dim vypisovac As Vypisovac = New Vypisovac()
    Dim vlakno1 As Thread = New Thread(AddressOf vypisovac.Vypisuj0)
    Dim vlakno2 As Thread = New Thread(AddressOf vypisovac.Vypisuj0)
    vlakno1.Start()
    vlakno2.Start()
    Console.WriteLine("Hotovo")
    Console.ReadKey()
End Sub

Výstup aplikácie je nasledovné:

Viacvláknové aplikácie vo VB.NET bez Join - Paralelné programovanie a viacvláknové aplikácie v VB.NET

"Hotovo" sa vypísalo ako prvý, pretože hlavné vlákno nečakalo na vypisovací vlákna. Na dokončenie činnosti vlákna môžeme počkať a to pomocou metódy Join (), ktorá zablokuje aktuálne vlákno, kým sa metóda nedokončí. Upravme náš kód do nasledujúcej podoby:

Sub Main()
    Dim vypisovac As Vypisovac = New Vypisovac()
    Dim vlakno1 As Thread = New Thread(AddressOf vypisovac.Vypisuj0)
    Dim vlakno2 As Thread = New Thread(AddressOf vypisovac.Vypisuj0)
    vlakno1.Start()
    vlakno2.Start()
    vlakno1.Join()
    vlakno2.Join()
    Console.WriteLine("Hotovo")
    Console.ReadKey()
End Sub

Hlavné vlákno teraz čaká až obe vlákna dokončí svoju prácu. Výsledok je nasledujúci:

Viacvláknové aplikácie vo VB.NET s Join - Paralelné programovanie a viacvláknové aplikácie v VB.NET

Ak by sme chceli nejaké vlákno uspať na dlhú dobu, môžeme miesto prepočítavanie hodín na sekundy odovzdať v parametri inštancii TimeSpan. Trieda TimeSpan má statické metódy ako FromHours () a podobne:

Thread.Sleep(TimeSpan.FromHours(2))

Ak chceme, aby systém nejaké vlákno prepol, môžeme ho nechať spať aj na 0 ms. Samotné volanie Thread.Sleep () vlákno vždy zablokuje. Podobný efekt môžeme dosiahnuť pomocou metódy Thread.Yield ().

Na stav vlákna sa môžeme spýtať pomocou jeho vlastnosti ThreadState. Je to flag nadobúdajúcej jednej alebo viacerých z týchto hodnôt: Running, StopRequested, SuspendRequested, Background, Unstarted, Stopped, WaitSleepJoin, Suspended, AbortRequested, Aborted. Túto vlastnosť používame najmä pri ladení, na synchronizáciu sa nehodí.

Zdieľanie dát medzi vláknami

Často samozrejme potrebujeme medzi vláknami zdieľať nejaké dáta a to minimálne kvôli komunikácii. Určite vás neprekvapí, že ak spustíme tú istú metódu vo viacerých vláknach, v každom vlákne bude mať svoje vlastné lokálne premenné. K jednoduchému pokusu využime triedu z minulého príkladu:

Dim vypisovac As Vypisovac = New Vypisovac()
Dim vlakno1 As Thread = New Thread(AddressOf vypisovac.Vypisuj0)
vlakno1.Start()
vypisovac.Vypisuj0()
Console.ReadKey()

výsledok:

Viacvláknové aplikácie vo VB.NET - Paralelné programovanie a viacvláknové aplikácie v VB.NET

Keďže konzola má v predvolenom stave 80 znakov a sú vypísané necelé 3 riadky, vidíme, že oba cykly prebehli 100x a že každé vlákno použilo svoju premennú i.

ThreadSafety

Metóda vlákna môže pristupovať k inštančným alebo statickým premenným. Práve týmto spôsobom spolu môžu jednotlivé vlákna komunikovať. Ako už tušíme, háčik bude v už spomínanej synchronizáciu.

Predstavme si nasledujúce triedu:

Public Class BankomatUnsafe
    Private hotovost As Decimal = 100
    Private Sub vyber100()
        If hotovost >= 100 Then
            Console.WriteLine("Vybírám 100")
            hotovost = hotovost - 100
            Console.WriteLine("na účtu máte ještě {0}", hotovost)
        End If
    End Sub
    Public Sub VyberVlakny()
        Dim vlakno As Thread = New Thread(AddressOf vyber100)
        vlakno.Start()
        vyber100()
        If hotovost < 0D Then
            Console.WriteLine("Hotovost je v mínusu, okradli nás.")
        End If
    End Sub
End Class

Trieda reprezentuje bankomat, ktorý eviduje nejakú hotovosť. Tá je pri vytvorení bankomatu 100 Sk. Ďalej disponuje jednoduchou metódou Vyber100 (), ktorá vyberie 100 korún v prípade, že je na účte potrebný zostatok. Zaujímavá je pre nás metóda VyberVlakny (), ktorá sa pomocou 2 vlákien (aktuálneho a novovytvoreného) pokúsi vybrať 100 Sk. Ak sa s hotovosťou náhodou dostaneme do mínusu, vypíšeme o tom hlásenie.

Do hlavnej metódy pridáme kód, ktorý vykoná 200 výberov na 100 bankomatoch:

For index = 0 To 100
    Dim bankomat As BankomatUnsafe = New BankomatUnsafe()
    bankomat.VyberVlakny()
Next

A aplikáciu spustíme:

Threadsafety v VB.NET - Paralelné programovanie a viacvláknové aplikácie v VB.NET

A z výpisu vidíme, že niečo nesedí. Kde je problém?

V metóde Vyber100 () kontrolujeme podmienkou, či je na účte dostatočná hotovosť. Predstavte si, že je na účte 100 Sk. Podmienka teda platí a systém vlákno uspí treba ihneď za vyhodnotením podmienky. Toto vlákno teda čaká. Druhé vlákno tiež skontroluje podmienku, ktorá platí, a odpočíta 100 Sk. Potom sa prebudí prvé vlákno, ktoré je už za podmienkou a tiež odpočíta 100 Sk. Vo výsledku máme na účte teda záporný zostatok! Vidíme, že práca s vláknami prináša nová úskalia, s ktorými sme sa doteraz ešte nestretli. Situáciu vyriešime pomocou zamykania.

Zamykanie (lock)

Iste sa zhodneme na tom, že sekcia s overením zostatku a jeho následnú zmenou musí prebehnúť vždy celá, inak sa dostávame do vyššie uvedenej situácie. Problém vyriešime tým, že sekciu, kde sa zdieľanou premennou zostatok pracujeme, opatríme zámkom. Kód upravíme do nasledujúcej podoby:

Public Class BankomatSafe
    Private hotovost As Decimal = 100
    Private zamek As Object = New Object()

    Private Sub vyber100()
        SyncLock zamek
            If hotovost >= 100 Then
                Console.WriteLine("Vybírám 100")
                hotovost -= 100
                Console.WriteLine("na účtu máte ještě {0}", hotovost)
            End If
        End SyncLock
    End Sub

    Public Sub VyberVlakny()
        Dim vlakno As Thread = New Thread(AddressOf vyber100)
        vlakno.Start()
        vyber100()
        If hotovost < 0D Then
            Console.WriteLine("Hotovost je v mínusu, okradli nás.")
        End If
    End Sub
End Class

Zamknutie vykonáme pomocou konštrukcie lock, ktorá berie ako parameter zámok. Zámkom môže byť ľubovoľný objekt, my si za týmto účelom vytvoríme jednoduchý atribút. Keď bude chcieť systém vlákno uspať, musí počkať, až sa dostane z kritickej sekcie (z tej pod zámkom).

Aplikácia teraz funguje ako má a my ju môžeme vyhlásiť za tzv. ThreadSafe (bezpečnú z hľadiska vlákien).

Threadsafe aplikácie vo VB.NET - Paralelné programovanie a viacvláknové aplikácie v VB.NET

V budúcej lekcii, Vlákna - Bezpečnosť vlákien vo VB.NET , sa budeme zaoberať bezpečnosťou vlákien vo VB.NET. Vyberieme si jednu techniku zdieľania dát medzi vláknami, ktorú implementujeme do príkladu.


 

Predchádzajúci článok
Úvod do viacvláknových aplikácií v VB.NET
Všetky články v sekcii
Paralelné programovanie a viacvláknové aplikácie v VB.NET
Preskočiť článok
(neodporúčame)
Vlákna - Bezpečnosť vlákien vo VB.NET
Článok pre vás napísal Ondřej Štorc
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje C#, HTML a CSS. Snaží se proniknout do tajů PHP, JS a ASP.NET
Aktivity