Poznáváme C# a Microsoft.NET – 51.díl – použití vláken III.

Po seznámení se s pojmem synchronizace vláken a s tím související třídou System.Threading.Monitor bych v dnešním díle rád představil další možné využití této třídy.

Test na získání zámku objektu

V minulém díle, ve kterém jsme se seznámili s třídou Monitor a jejími metodami Enter a Exit, některé z vás možná napadlo, jestli je nějakým způsobem možné zjistit, zda-li je možné získat exklusivní zámek toho čí onoho objektu. Pokud bychom chtěli tuto informaci získat a v případě, že je zámek k dispozici, tento zámek použít k synchronizaci použijeme k tomu metodu TryEnter třídy Monitor.

/// <summary>
/// Ukazka pouziti metody TryEnter tridy Monitor
/// </summary>
internal class TryEnterExam
{
  //kolekce, ke ktere je vlakny pristupovano synchronizovane
  private ArrayList elements;
   
  internal TryEnterExam()
  {
    elements = new ArrayList();
  }

  private bool TryToAddElement(object Element)
  {
    //otestuje zda je mozne ziskat zamek objektu elements
    if (Monitor.TryEnter(elements))
    {
      //pokud se podari ziskat zamek pridame element
      elements.Add(Element);
      return true;
    }
    else
    {
      return false;
    }
  }
}

V příkladu se zkouší získat zámek pro objekt kolekce za účelem přidání dalšího prvku. Pokud se podaří daný zámek získat, je další objekt do kolekce přidán, v opačném případě se do kolekce nic nepřidá a je vrácena hodnota false, aby volající byl informován o tom, že se přidání prvku zdařilo.

V uvedeném zdrojovém kódu je použita nejjednodušší přetížená verze metody TryEnter , které je předán pouze objekt, pro který chceme zkusit zámek získat. Ovšem kromě této verze je nám k dispozici ještě jedna zajímavá verze této metody a ta kromě objektu přijímá ještě čas, po který má vlákno čekat na získání zámku. Takže pokud bychom řádek s voláním metody TryEnter změnili do této podoby, vlákno by zkusilo získat zámek objektu a pokud by se tak hned nepovedlo, počkalo by ještě jednu vteřinu, jestli nebude zámek uvolněn.

private bool TryToAddElement(object Element)
{
  /*otestuje zda je mozne ziskat zamek objektu elements,
   * pokud ne, bude se vterinu cekat, jestli nedojde k jeho
   * uvolneni*/
  if (Monitor.TryEnter(elements, 1000))
  {
    //pokud se podari ziskat zamek pridame element
    elements.Add(Element);
return true;
  }
  else
  {
    return false;
  }
}

Notifikace vláken a čekání na zámek

Zatím jsme se zabývali pouze tím, jak je možné získávat či uvolňovat zámky objektů a jejich uvolňováním, takže jsme v našich příkladech získali zámek, provedli požadované operace a po té zámek uvolnili. Podívejme se na následující zdrojový kód, který by již pro nás měl být lehce pochopitelný.

internal class NotPulsingExam
{
  internal static void Run()
  {
    Thread.CurrentThread.Name = "Prvni vlakno";
    Thread lSecond = new Thread(new ThreadStart(DoSomeWork));
    lSecond.Name = "Druhe vlakno";
    lSecond.Start();
    DoSomeWork();
  }
   
  internal static void DoSomeWork()
  {
    //ziskani zamku
    lock(typeof(NotPulsingExam))
    {
      for (int i = 0; i < 3; i++)
      {
        Console.WriteLine("{0} - {1}", Thread.CurrentThread.Name, i);
      }
    }
    //uvolneni zamku
  }
}

Předpokládejme, že bychom chtěli ,aby se tato dvě vlákna ve výpisu na systémovou konzoli střídala po vypsání jednotlivého znaku. K tomu abychom tohoto dosáhli, potřebujeme po vypsání jednotlivého znaku probudit další vlákno, které čeká na uvolnění zámku objektu a také je potřeba, aby vlákno, které již vypsalo znak, uvolnilo držený zámek. Jak na to se snaží ukázat tento příklad.

/// <summary>
/// Ukazkovy priklad na notifikaci vlaken, pomoci metody Pulse
/// tridy Monitor
/// </summary>
internal class PulsingExam
{
  internal static void Run()
  {
    Thread.CurrentThread.Name = "Prvni vlakno";
    PulsingExam lInstance = new PulsingExam();
    Thread lSecond = new Thread(new ThreadStart( lInstance.DoSomeWork));
    lSecond.Name = "Druhe vlakno";
    lSecond.Start();
    lInstance.DoSomeWork();
  }
   
  internal void DoSomeWork()
  {
    lock(this)
    {
      for(int i = 0; i < 3; i++)
      {
        Console.WriteLine("{0} - {1}", Thread.CurrentThread.Name, i);
        Monitor.Pulse(this);
        //zabraneni blokaci zamku (deadlocku)
        if (i < 2)
        {
          Monitor.Wait(this);
        }
      }
    }
  }
}

K tomu, abychom dosáhli výše zmíněného požadavku, použijeme metody Pulse a Wait třídy Monitor. Zavoláním metody Pulse totiž zařídíme, že je vzbuzeno další vlákno čekající na zámek objektu, jakmile jej aktuální vlákno uvolní. K tomu, aby aktuální vlákno uvolnilo držený zámek objektu slouží metoda Wait, která kromě operace uvolnění zámku zapříčiní i uspání aktuálního vlákna (bude čekat na opětovné uvolnění zámku vláknem jiným). Zámek držený aktuálním vláknem je nutné uvolnit, aby mohlo probuzené vlákno vstoupit do synchronizovaného bloku kódu.

Možná se podivujete nad podmínku kontrolující hodnotu proměnné i, která je použita v metodě DoSomeWork. K tomu, abychom mohli problém, který by mohl vzniknout, lépe pochopit se zkusme podívat na následující příklad.

/// <summary>
/// Ukazka mozneho zpusobeni blokace zamku metodou Pulse.
/// Spusteni tohoto prikladu zpusobi, ze program neskonci.
/// </summary>
internal class PulsingWithDeadlockExam
{
  internal static void Run()
  {
    Thread.CurrentThread.Name = "Prvni vlakno";
    PulsingWithDeadlockExam lInstance = new PulsingWithDeadlockExam();
    Thread lSecond = new Thread(new ThreadStart( lInstance.DoSomeWork));
    lSecond.Name = "Druhe vlakno";
    lSecond.Start();
    lInstance.DoSomeWork();
  }
   
  internal void DoSomeWork()
  {
    lock(this)
    {
      for (int i = 0; i < 3; i++)
      {
        Console.WriteLine("{0} - {1}", Thread.CurrentThread.Name, i);
        //probuzeni dalsiho vlakna
        Monitor.Pulse(this);
        //vzdani se zamku k objektu
        Monitor.Wait(this);
      }
      Console.WriteLine("Vlakno {0} skoncilo svou cinnost", Thread.CurrentThread.Name);
    }
  }
}

Pokud si zkusíte spustit tento příklad, zjistíte, že je běh programu, po vypsání čísel na systémovou konzoli, zablokován. Proč je tomu tak? Je to z důvodu, že při běhu tohoto příkladu dojde ke vzniku situace zvané deadlock (zablokované zámky) a to protože dojde k situaci, ve které jedno vlákno čeká na probuzení jiným vláknem, což se ale nikdy nestane, protože vlákno, které by mělo zavolat probuzení je již ukončeno.

Tomu je právě předcházeno použitím podmínky v předchozím příkladu, která zajistí, že pokud je prováděna poslední iterace práce vlákna, není již zavolána metoda Wait, aby nedošlo k nechtěnému zablokování (vlákno již nepotřebuje být znovu probuzeno, protože má svou práci hotovou a může skončit).

Dalším možným řešením tohoto problému je v určitých situacích použití přetížené verze metody Wait, které předáme čas, po který má čekat na probuzení jiným vláknem. Po vypršení tohoto času se totiž blokované vlákno přesune do aktivního stavu za účelem získaní zámku a pokračování v činnosti. A jelikož mu v našem příkladu v získání zámku již nebude jiné vlákno bránit (druhé vlákno již svou činnost skončilo), spuštění příkladu již neskončí zablokováním. Schválně si zkuste v příkladu ukazující vznik zablokování, změnit řádek s voláním metody Wait do této podoby.

Monitor.Wait(this, 1000);

Příklady související s článkem jsou k dispozici zde.

Témata článku: Software, Microsoft, Programování, Elements, Puls, Pulse, Element

3 komentáře

Nejnovější komentáře

  • Pavel Polívka 24. 11. 2007 13:46:29
    Programovou oflline verzi seriálu naleznete ke stažení na...
  • Lubos Hladik 14. 7. 2006 9:20:11
    Zdravim,
    nemelo by u prvnich dvou prikladu v tomto clanku pri splneni...
  • Silvius 2. 12. 2005 17:47:32
    A nestacilo by nodifikovat metodu DoSomeWork nasledovnym...
Určitě si přečtěte

Operační systém běžným počítačům nedal Bill Gates, ale Gary Kildall

Operační systém běžným počítačům nedal Bill Gates, ale Gary Kildall

** Gary Kildall pochopil, že levné výpočetní čipy mohou posloužit jako univerzální počítače pro všechny ** Připravil pro ně proto první operační systém ** Později mu systém vyfoukl Microsoft a nazval ho MS DOS

23.  4.  2017 | Pavel Tronner | 57

Umělá inteligence je sice v plenkách, už teď ale přestáváme rozumět, jak vlastně funguje. To je problém

Umělá inteligence je sice v plenkách, už teď ale přestáváme rozumět, jak vlastně funguje. To je problém

** Už je to tady, lidé přestávají chápat počítače ** Systémy neuronových sítí začínají pracovat tak, že ani jejich tvůrci přesně neví, co se uvnitř děje ** Do budoucna to může být závažný problém

24.  4.  2017 | Jakub Čížek | 112

Před 35 lety měl premiéru legendární počítač ZX Spectrum. Připomeňte si „Gumáka“

Před 35 lety měl premiéru legendární počítač ZX Spectrum. Připomeňte si „Gumáka“

** Slavný osmibitový počítač Sinclair ZX Spectrum byl uveden právě před 35 lety ** Připomeňte si tento průkopnický počítač v tematických článcích ** Podívejte se, jak funguje dnes

23.  4.  2017 | Pavel Tronner | 13

Acer chrlí novinky: levný a tenký Predator, nové Switche a další notebooky

Acer chrlí novinky: levný a tenký Predator, nové Switche a další notebooky

** Acer na konferenci v New Yorku představil velkou spoustu novinek z oblasti počítačů, notebooků i monitorů ** Notebookové novinky se dotkly řad Predator, Swift, Switch i Aspire ** Herní notebooky dostaly nový typ chlazení

27.  4.  2017 | Karel Javůrek | 7

Správný počítač má alespoň dva monitory. Anebo je to jinak?

Správný počítač má alespoň dva monitory. Anebo je to jinak?

** David si nedokáže představit práci bez dvou a více monitorů ** Kubovi naopak stačí jeden a ve více displejích se ztrácí ** Jaký přístup je lepší?

23.  4.  2017 | Jakub Čížek | 59


Aktuální číslo časopisu Computer

Supertéma: moderní cestování

Kdy opravdu přijdou nové baterie?

Velké testy: 6 herních notebooků a 8 volantů

Recenze: AMD Ryzen řady 5