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, 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

Sbíječky vyměnili za klávesnice. Nový projekt má za cíl přeučit horníky na programátory

Sbíječky vyměnili za klávesnice. Nový projekt má za cíl přeučit horníky na programátory

** Programátorů je málo a horníků bez práce po uzavření dolu Paskov bude moc ** Problém řeší unikátní projekt ** Pilotní kurz dává naději, že by z horníků mohli být použitelní kodéři

28.  11.  2016 | David Polesný | 78

ASUS ZenBook 3 se začal prodávat v Česku. Je ve všem lepší než MacBook, ale bude to stačit?

ASUS ZenBook 3 se začal prodávat v Česku. Je ve všem lepší než MacBook, ale bude to stačit?

** Novinka od Asusu míří přímo proti MacBooku od Applu ** Nabídne daleko více výkonu za stejné peníze

2.  12.  2016 | David Polesný | 123