4 Şubat 2016 Perşembe

Posix Eşzamanlılık Yapıları

Posix Eşzamanlılık Yapıları
Aşağıda bazı Posix Eşzamanlılık Yapılarına göz atacağız. Aslında bu yazı çok karışık bir hale geldi. Zamanla temizlemeyi umuyorum.


Posix Eşzamanlılık Yapıları ve Memory Barrier

Derleyici tarafından hazırlanan makine kodu CPU tarafından aynı sıra ile çalıştırılmayabilir. CPU'lar kendi içlerinde bir çok kademeli önbellek te kullandıkları için donanım seviyesinde birden fazla CPU'nun aynı değeri kullanmama olasılığı ortaya çıkar. Bu durumu kontrol etmek için Memory Barrier yapıları ortaya çıkmıştır.Usage of registers by the compiler in multithreaded program sorusuna da göz atarsak benzer bir açıklama görülebilir.

Memory barrier başlıklı yazıdaki şu cümleye dikkat etmek lazım.
Primitives such as mutexes and semaphores are provided to synchronize access to resources from parallel threads of execution. These primitives are usually implemented with the memory barriers required to provide the expected memory visibility semantics. In such environments explicit use of memory barriers is not generally necessary.
Buradaki soruda memory senkronizasyonuna sebep olan POSIX metodlarının listesi bulunabilir.


Posix Eşzamanlılık Yapıları ve Bekletme Kuyrukları  (Wait Queue)

Posix Eşzamanlılık Yapılarına göz atmaya başlamadan önce bilinmesi gereken ortak bir konu var. Bu da bir çok Posix Eşzamanlılık Yapısının içinde "bekletme kuyrukları"'nın olması.  Yapıyı kilitlemeye çalışan thread eğer beklemek zorundaysa bu kuyruğa alınıyor ve bekletiliyor. In linux scheduler, does it track the current sleeping task or killed task? sorusunda ve aşağıdaki Mutex paragrafında benzer açıklamaları okuyabilirsiniz. 

Bariyerler
Posix Eşzamanılık Yapıları - Barrier yazısına taşıdım.

Read Write Lock
Posix Eşzamanlılık Yapıları - Read Write Lock yazısına taşıdım.

Semaphore
Posix Eşzamanlılık Yapıları - Semaphore yazısına taşıdım.


Mutex

Genel kullanım amacı bir kaynağa erişimi kontrol altına alıp sadece tek bir kişinin erişimin sağlamak, diğer erişmek isteyenleri bekletmektir.

pthread_mutex_t aslında bir structure olarak tanımlı. Burada da anlatıldığı gibi bu structure şeffaf değil. İçindeki alanlar gerçekleştiren kütüphaneye göre değişebilir ve programcıyı ilgilendirmez.


Mutex'in Özellikleri

POSIX ile mutex'in bazı özellikleri tanımlanmış. Bunlar  mutex'in priority, protocol, pshared, type alanları. Tüm alanlar her platformda yok. Aşağıda bazı özellikleri açıklamaya çalıştım.


Mutex Tipleri
Mutex tiplerini anlatan yazı burada.

PTHREAD_MUTEX_DEFAULT veya PTHREAD_MUTEX_NORMAL :
Mutex aynı thread tarafından sadece bir kere kilitlenebilir. İkinci kilitmede deadlock oluşur.

PTHREAD_MUTEX_ERRORCHECK :
Mutex aynı thread tarafından sadece bir kere kilitlenebilir. İkinci kilitmede deadlock oluşmaz ama hata döner.

PTHREAD_MUTEX_RECURSIVE:
Mutex aynı thread tarafından defalarca kilitlenebilir.

PTHREAD_MUTEX_ROBUST:
Bu mutex çeşidi processler arası kullanılabiliyor.


Non Recursive Statik Mutex
Non Recursive mutex'lere aynı zamanda fast mutex te denildiğini gördüm. Şöyle yaratılır.

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
Burada dikkat edilmesi gereken nokta PTHREAD_MUTEX_INITIALIZER aslında {0,0,0,0,0{0}} şeklinde bir C macrosu. Buraki uzun tartışamlardan da görülebileceği gibi bu macroyu kullanmak aslında pthread_mutex_init() metodunu attr parametresi NULL atanmış şekilde çağırmakla aynı.


Metod içinde kullanmak istiyorsak aşağıda da anlattığım gibi pthread_mutex_init( &(mu), NULL); şeklinde yapmak lazım.

Non Recursive Dinamik Mutex
Mutex'i kullanmaya başlamadan önce pthread_mutex_init metodu ile onu başlatmak gerekiyor.
pthread_mutex_init metodun imzası burada.
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                       const pthread_mutexattr_t *restrict attr);
Şöyle kullanırız.
pthread_mutex_t lock;
pthread_mutex_init (&lock, NULL);
Eğer ikinci parametreye NULL geçersek, POSIX kütüphanesinin gerçekleştirmesine bağımlı olan bir default davranış elde ederiz. Default davranış ta non-recursive mutex'tir. Ancak bu davranış platforma göre değişebiliyor denmiş fakat ben bunu gözlemleyemedim . O yüzden emin olmak adına NULL kullanmamak daha iyi.

pthread_mutexattr_init() metodunu kullanarak attributes nesnesine değer atamak daha iyi. Bu metod ile attributes nesnesinin priority, protocol, pshared, type alanlarına değer atanıyor.

pthread_mutexattr_init
Bu metod ile mutex'e özellik atamak mümkün. Örnek:

pthread_mutexattr_t matr; //Mutex Attribute
pthread_mutexattr_init(&matr);
pthread_mutexattr_setpshared(&matr,PTHREAD_PROCESS_SHARED);

Mutex ve Bekleme Listesi
Buradaki linux çekirdek kodunda (mutex.h) mutex içindeki wait_list listesinin de pthread_mutex_init ile başlatıldığını görmek ilginçti.
veya
// Create the mutex attributes.
pthread_mutexattr_t attributes;
pthread_mutexattr_init( &attributes );
pthread_mutexattr_settype( &attributes, PTHREAD_MUTEX_NORMAL );

// Create the mutex using the attributes from above.
pthread_mutex_t mutex;
pthread_mutex_init( &mutex, &attributes );
Recursive Static Mutex
pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
Recursive Dinamik Mutex
Aşağıdaki kod parçası ise recursive bir mutex yaratır.
Burada mutex tipi olarak PTHREAD_MUTEX_RECURSIVE kullanılır. Bazı Linux kodlarında  PTHREAD_MUTEX_RECURSIVE_NP (NP non portable anlamına geliyor) da kullanılıyor ancak recursive dinamik mutexler gerçekten non-portable mı değilmi ben de bilmiyorum.

pthread_mutex_lock metodu
Klasik kullanımı şöyle
pthread_mutex_lock(&mutex);
Bu metoda unlock eşlik etmeli.
pthread_mutex_lock(&queue_mutex);
...
    
pthread_cond_signal(&proc_ready);
pthread_mutex_unlock(&queue_mutex);
pthread_mutex_lock metodu
Klasik kullanımı şöyle
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy
Buradaki soruda bu metodun çağırılıp çağrılmamasının etkisi uzun uzun tartışılmış. Benim anladığım POSIX bir çok platformda farklı farklı gerçekleştirilmiş olabilir. Çağrılsa iyi olur. Örneğin Linux'ta bu metod çok bir iş yapmıyor gibi görünüyor ancak, Windows'ta CloseHandle metodunu çağırarak bir kernel nesnesini serbest bırakıyor.

Robust Mutex
Mutex'in attribute özelliğine aşağıdaki gibi bir değer atarsak o mutex robust oluyor. Ancak tam olarak ne işe yarıyor bilmiyorum.
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST)

Mutex ve Bekleme Listesi
Mutex içinde burada anlatıldığı gibi Compare and Swap metodları kullanılır. Eğer mutex kilitlenemiyorsa futex_wait metodu ile thread bekleme listesine alınır.

İlginç bir konu ise, mutex üzerinde bekleyen thread'lerden hangisinin uyandırılması gerektiği. Mutexes and thread priorities with regards to scheduling on POSIX systems başlıklı yazıdan anladığım kadarıyla bu konuda net bir tanımlama yapılmamış ancak yine aynı yazıda da görülebileceği gibi Linux üzerinde mutex için thread önceliğine göre sıralanmış bir kuyruk var ve uyandırılması gereken thread önceliği en yüksek olan olarak seçiliyor.

Posix mutex API'si

int pthread_mutex_trylock(pthread_mutex_t *mutex) : Verilen mutex'i kilitmeye çalışır.
int pthread_mutex_timedlock(pthread_mutex_t* mutex, struct timespec* abstime) : Verilen mutex'i belirtilen süre içinde kilitmeye çalışır

STL ve mutex

unique_lock
lock_guard sınıfının aksine, mutex'i bırakıp tekrar alabilme imkanı sunar. Dolayısıyla condition değişkenleri ile kullanıma uygundur.

Konu için boost'a bakınız. Örnek:

boost ve mutex
boost ile mutex tanımlamak ve kullanmak çok kolay. Posix API'sini bilmeye gerek yok. Örnek

boost::lock_guard<boost::mutex> lock(_mutex);
Aşağıdaki şekilde boost::lock_guard'ın kütüphane ile gelen tüm mutex çeşitleri ile kullanılabileceğini görmek mümkün.
boost::mutex
boost::mutex aynı thread tarafından  sadece bir defa kilitlenebilir.

boost::mutex m;

void PublicFunc() {
 m.lock();

 //Kilidi kaldır
 m.unlock();
}

boost::recursive_mutex
Aynı thread tarafından birden çok defa kilitlenebilir

boost::named_recursive_mutex
Process'ler arası kulllanılır. Birden çok defa kilitlenebilir

boost::shared_mutex
Read/Write kilitleri için kullanılıyor.


Condition
Condition da mutex gibi static olarak yaratılabiliyor. Örnek

pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;
Veya dinamik olarak yaratılmak istenirse aşağıdaki gibi API kullanılabilir.


pthread_cond_wait
pthread_cond_wait yazısına taşıdım.

pthread_cond_timedwait
Koşul tetikleninceye veya zaman doluncaya kadar bekler.
boost
Örnek:
boost::system_time const timeout=boost::get_system_time()+ boost::posix_time::milliseconds(35000);
boost::mutex::scoped_lock lock(the_mutex);
if(the_condition_variable.timed_wait(lock,timeout,&CondFulfilled))
{
    //<cond fulfilled code>
}
else
{
    //<timeout code>
}

  
pthread_cond_signal
Condition değişkenleri mutex olmadan kullanılmıyor.
Buradaki örnekte, bir condition değişkeni sinyanllenmeden önce mutex'i kilitlemek daha sonra ise, mutex'i bırakmak gerektiği açıklanmış.Örnek:


Memory Barrier
Memory Barrier'leri ile thread'ler arasında senkronizasyonu sağlayan bariyerler ismen benzeseler de amaçları farklı. Memory Barrier'leri lock-free kod yazmak için kullanılır.
C#
C# ile Thread.MemoryBarrier() metodu kullanılabiliyor. Örneği buradan aldım.

POSIX Threadleri
pthread_join
Başlatılmış olan bir thread'in bitmesi aşağıdaki gibi beklenebiliyor.
 
pthread_cancel
pthread_cancel ile bir başka thread'a iptal sinyali gönderiliyor. Sinyali alan thread çalışırken herhangi bir cancellation point noktasına gelirse çalışmayı durduruyor. Linux ve Solaris arasında cancellation point tanımları arasında fark var. pthread_cancel don't work under solaris başlıklı soruda benzer bir açıklama var.

boost
boost threadlerini iptal etmek için boost::thread::interrupt() metodu kullanılıyor. Bu metod ile herhangi bir intterruption pointte bekleyen thread'ler iptal edilebiliyor. interruption point'leri buradan görebilirsiniz.
Bu metodun pthread_cancel() ile aynı işi yaptığını tahmin ediyorum.

Java 
pthread_cancel ile Java'daki interrupt() metodu benzer bir mantıkla çalışıyor. Dealing with InterruptedException başlıklı yazıda  WAITING or TIMED_WAITING durumunda bekleyen threadlerin interrupt edilebildikleri ve edildikten sonra aşağıdaki gibi interrupt edildiğini gösteren bayrağı tekrar kaldırmalarının faydalı olduğu anlatılıyor. Benzer bir açıklama da burada var. Yine benzer bir açıklama burada var.

Burada dikkat edilmesi gereken şey thread'i WAITING veya TIMED_WAITING durumuna sokan metodlar. 

----------
Bu metodlar neler ? Aşağıda bazı metodlar var ancak tam olarak hangisi BLOCKED hangisi TIMED_WAIT durumuna sokuyor bilmiyorum. Bu konuyu detaylandırmam lazım.

wait(), Thread.sleep(), BlockingQueue.get(), Semaphore.acquire() and Thread.join() gibi metodlar ve synhronized anahtar kelimesinin kullanılması  thread BLOCKED durumuna sokuyor

------------
Aşağıda Thread'in durumunu gösteren bir şekil var.

 Why Are Thread.stop, Thread.suspend,Thread.resume and Runtime.runFinalizersOnExit Deprecated?
yazısında da interrupt() metodunun WAITING veya TIMED_WAITING durumunda bekleyen thread'lerde işe yarıyor olduğu açıklanıyor.

Bir diğer önemli nokta ise eğer istenirse Thread.currentTread().stop() metodu da aynı thread içinde çağırılmak kaydıyla kullanılabilir.

pthread_kill
Bu metod thread'i ölmeye zorluyor sanırım. Bir threadi ölmeye zorlamanın etkisini tahmin edilemez olduğunu buradan okuyabilirsiniz. Bloke olmuş bir thred'i döngü içinde durmaya zorlamak en temiz çıkış yolu.

Hiç yorum yok:

Yorum Gönder