poniedziałek, 11 lutego 2013

[C#|Visual Studio] Threading: Synchronizacja

Synchronizacja wątków jest szczególnie ważna, gdy korzystają one z tych samych zasobów, jednak niezsynchronizowane wątki mogą także w wielu przypadkach doprowadzić do nieprzewidywalnego zachowania programu. Konstrukcje koordynujące wątki dzieli się na cztery kategorie:

Blokowanie

Wątek zablokowany, to taki, którego wykonywanie z jakiegoś powodu zostało wstrzymane, na przykład poprzez operację Sleep bądź Join względem innego wątku. Wątek w stanie zablokowania oddaje swoją część procesora do momentu wznowienia. Odblokować wątek można na cztery sposoby:
  • warunek wznowienia zostaje spełniony
  • przekroczony timeout (jeżeli został ustawiony)
  • przerwanie przez Thread.Interrupt
  • wykonanie instrukcji Thread.Abort
Podczas blokowania wątków można spotkać także tzw. spinning, technikę bardzo niefektywną dla CPU polegającą na ciągłym sprawdzaniu pewnego warunku. Nieco lepszym rozwiązaniem jest usypianie wątku na krótki czas. Technika ta jest efektywna jedynie dla krótkich blokowań, rzędu milisekund, gdyż wtedy nie zachodzi konieczność cennego przełączania kontekstu.

while (!proceed) Thread.Sleep (10);

Stan wątku jest dostępny w każdej chwili za pomocą property ThreadState. Możliwe stany wątków przedstawione są na diagramie poniżej.


Wątki zostają zablokowane na przykład, gdy użyjemy instrukcji lock. Instrukcja ta jest skrótem składniowym odpowiadającym instrukcjom Monitor.Enter oraz Monitor.Exit. Obiekt na którym wykonywana jest blokada musi dziedziczyć po typie referencyjnym.

static readonly object _locker = new object();
static int _x;

static void Increment() 
{ 
    lock (_locker) _x++;
}

static void Decrement()
{
    lock (_locker) _x--;
}

public static void Run()
{
    for (int i = 0; i < 5; i++)
    {
        Task.Factory.StartNew(Increment);
        Task.Factory.StartNew(Decrement);
    }
}

Lock jest instrukcją atomową, chyba że w jego wnętrzu zostanie rzucony wyjątek.Podobny efekt można uzyskać stosując Mutex. Konstrukcja ta mimo iż jest kilkadziesiąt razy wolniejsza, ma jedną ważną przewagę nad lockiem - można za jej pomocą wykonywać blokady pomiędzy procesami.

static void RunMutex()
{
    using (var mutex = new Mutex(false, "Sample Mutex String"))
    {
        if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false))
        {
            Console.WriteLine("Another app instance is running. Bye!");
            return;
        }
        RunProgram();
    }
}

static void RunProgram()
{
    Console.WriteLine("Running. Press Enter to exit");
    Console.ReadLine();
}

Nazwa Mutex-a pozwoli mu blokować procesy w obrębie całego komputera. Jeżeli powyższy program zostanie uruchomiony w dwóch instancjach, to druga instancja poczeka 3 sekundy i zakończy swoją pracę, jeżeli kod pierwszej instancji nie wykona się (nie zostanie wciśnięty Enter).

Semafor

Semafor działa jak lock, z tą różnicą że jest w stanie obsłużyć więcej niż jeden wątek. Liczba wątków definiowana jest przez programistę, a każdy nadmiarowy wątek musi poczekać, aż któryś z poprzedników skończy swoje działanie.

static SemaphoreSlim _sem = new SemaphoreSlim(3);
public static void Run()
{
    for (int i = 1; i <= 5; i++) 
        new Thread(Enter).Start(i);
}

static void Enter(object id)
{
    Console.WriteLine(id + " wants to enter");
    _sem.Wait();
    Console.WriteLine(id + " is in!");
    Thread.Sleep(1000*(int) id);
    Console.WriteLine(id + " is leaving");
    _sem.Release();
}

SemaphoreSlim to klasa z .NET 4.0 zoptymalizowana pod kątem czasu wykonywania operacji Wait i Release.

Brak komentarzy:

Prześlij komentarz