Pokazywanie postów oznaczonych etykietą lock. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą lock. Pokaż wszystkie posty

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.

sobota, 2 lutego 2013

[C#|Visual Studio] Threading: Podstawy

W języku C#, niezależnie od tego, czy tworzymy aplikację konsolową, WinFormsową czy WPFową, domyślnie operacje zawsze będą się wykonywały w jednym wątku, tworzonym przez CLR. Wątkami w .NEcie zarządza specjalny mechanizm zwany thread scheduler. Na jednordzeniowej maszynie wielowątkowość realizowana jest poprzez podział czasu pracy procesora, natomiast na wielordzeniowej wątki uruchamiane są na wszystkich rdzeniach, jednak należy pamiętać, że system operacyjny czy też inne uruchomione programy również korzystają z CPU, tak więc podział czasu i przełączanie wątków w praktyce odbywa się zawsze. Czas przełączenia szacuje się na dziesiątku milisekund.

Najprostszym sposobem na zrównoleglenie wykonywanych operacji jest klasa Thread. Poniższy przykład pokazuje, jak przy jej użyciu na wieloprocesorowej maszynie wykonywać operacje naprzemiennie.

public static void Run()
{
    Thread t = new Thread(WriteY);          
    t.Start();
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(i);
        Console.Write("x");
    } 
        
}

static void WriteY()
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(i);
        Console.Write("y");
    }
}

Warto pamiętać, że dla każdego wątku tworzony jest osobny memory stack. Informacje pomiędzy wątkami mogą być przekazywane np. poprzez zmienne statyczne. Nieumiejętne ich wykorzystanie może jednak spowodować poważne problemy. Aby uniknąć np. sytuacji, w której dwa wątki zapisują w tym samym czasie dane do jednej zmiennej, należy użyć tzw. Exclusive Lock. Użycie słowa kluczowego lock powoduje, że tylko jeden wątek w danej chwili może korzystać z zasobu, drugi musi poczekać.

public class LockTest
{
    static bool done;
    static readonly object locker = new object();

    public static void Run()
    {
        new Thread(Go).Start();
        Go();
    }

    static void Go()
    {
        lock (locker)
        {
            if (!done) { Console.WriteLine("Done"); done = true; }
        }
    }
}

Blokowany wątek nie zużywa zasobów CPU.

Kolejną ważną operacją jest Join, służący do synchronizowania kolejności wykonywania operacji. Jeżeli z poziomu jednego wątku startujemy inny podrzędny, możemy zażądać aby dalsze wykonywanie kodu wstrzymać do zakończenia działania tego podrzędnego wątku.

public class JoinTest
{
    public static void Run()
    {
        Thread t = new Thread(Go);
        t.Start();
        for (int i = 0; i < 100; i++) Console.Write("x");
        t.Join();
        Console.WriteLine("Thread t has ended!");
    }

    static void Go()
    {
        for (int i = 0; i < 1000; i++) Console.Write("y");
    }
}

Wątki można nazywać, co przydaje się podczas debugowania w Visual Studio. Można im także nadawać priorytety. Nadanie priorytetu decyduje o tym, ile czasu procesor poświęci na dany wątek. Domyślny priorytet to Medium.

public class ThreadNamePriorityTest
{
    public static void Run()
    {
        Thread.CurrentThread.Name = "Main Thread";
        Thread worker = new Thread(Go);
        worker.Name = "Worker";
        worker.Priority = ThreadPriority.BelowNormal;
        worker.Start();
        Go();
    }

    static void Go()
    {
        Console.WriteLine("Hello from " + Thread.CurrentThread.Name);
    }
}

czwartek, 5 lipca 2012

[Wzorce projektowe] Singleton

Analogia z życia:

Każde dziecko wie, że nieodłączną częścią każdego samochodu jest kierownica. Samochód zawiera dokładnie jedną kierownicę. Kierownica może być używana tylko przez jedną osobę, lub w ogóle nie używana. Gdyby kilka osób zaczęło kręcić kierownicą podczas jazdy to nietrudno sobie wyobrazić do czego by to doprowadziło. Gdyby samochód miał więcej kierownic. W najlepszym wypadku doszło by do czegoś takiego :-):



Zastosowanie:

Singletony tworzy się, gdy zachodzi potrzeba stworzenia dokładnie jednej instancji danej klasy. Dodatkowo wprowadza się blokowanie dwufazowe - zabezpieczenie instancji przed dostępem z wielu wątków. Obiekty - singletony podczas tworzenia nie potrzebują by przekazywać parametry za pomocą konstruktora. Czasami warto także zastanowić się nad stworzeniem singletonu gdy samo tworzenie obiektu jest kosztowne czasowo, a nie jest potrzebne wiele instancji.

Zasada działania:


Zazwyczaj tworzy się klasę zawierającą propercję lub polę pewnego typu. Klasa zewnętrzna posiada prywatny konstruktor i metodę zwracającą instancję obiektu z pola / propercji. Jeśli obiekt nie został wcześniej zainicjowany, to tworzy się instancję, która później jest zwracana przy każdym wywołaniu tej metody. Można użyć także klas statycznych, ale jest to odradzane ze względu na problemy z testowaniem i brak możliwości dziedziczenia.

Przykład:

Interfejs do zapisywania zmian do jakiegoś rejestru np logowanie wyjątków.

Przykład implementacyjny:

Poniższy singletonu wydaje się być optymalny ze względu na wydajność dla platformy .NET.




    public class ThreadSafeSingleton
    {
        private ThreadSafeSingleton()
        {
            
        }

        public static ThreadSafeSingleton Instance 
        {
            get { return NestedClass.instance; }
        }

        public void SayHello()
        {
            Console.WriteLine("How are you ?");
        }

        public class NestedClass
        {
            static NestedClass()
            {
            }

            internal static readonly ThreadSafeSingleton instance =
                new ThreadSafeSingleton();
        }
    }

class Program
    {
        static void Main(string[] args)
        {
            var inst = ThreadSafeSingleton.Instance;
            inst.SayHello();

        }
    }


Prywatny konstruktor zabrania tworzenia obiektów klasy. Statyczna propercja gwarantuje, że przekazywana będzie jedna referencja przy każdym wywołaniu, natomiast dzięki statycznemu konstruktorowi klasy zagdnieżdżonej obiekt zostanie zainicjowany dopiero przy pierwszym jego wywołaniu (Lazy Loading). Pole readonly zapewnia bezpieczeństwo przy pracy wielowątkowej i jest to podejście szybsze od stosowania instrukcji lock.

Linki:

1.Readonly a thread safe