5 Nisan 2018 Perşembe

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

Semaphore
Genel kullanım amacı bir kaynağa aynı anda kaç kişini erişebileceğini kontrol etmektir. Şu dosyayı include etmek gerekir.
#include <semaphore.h>
Posix ile iki çeşit semaphore kullanılabilmekte. İlki named semaphore, diğeri ise unnamed semaphore. Burada dikkat edilmesi gereken nokta unnamed semaphore uygulamalar arasından paylaşılamaz anlamına gelmiyor.

1. Named Semaphore
Bu semaphore hep bir pointer olarak tutuluyor.
sem_t *mySemaphore;
Named semaphore yapısına bir isim verilerek erişiliyor. ARINC 653 veya OS X gibi bazı işletim sistemleri sadece Named Semaphore kullanımını destekliyorlar.

POSIX standardında Named semaphore ile sem_open(),sem_close() ve sem_unlink() metodları kullanılıyor.

sem_open metodu
Bu yapıyı oluşturmak için sem_open metodu kullanılıyor. O_CREAT flag'i ile kullanılırsa, named semaphore yaratılmamışsa yaratılır.
const char* name = "/permission_test_semaphore";
sem_t* sem = sem_open(name, O_CREAT | O_EXCL, 0644, 0);
if (SEM_FAILED == sem){...}
Bir başka örnek
const char *semaphore_name = "my-test-semaphore";

int rc = sem_unlink(semaphore_name);
if (rc)
  perror("sem_unlink");

sem_t *semaphore = sem_open(semaphore_name, O_CREAT, O_RDWR, 0);
if (semaphore == SEM_FAILED) {
  perror("sem_open");
  return 1;
}

rc = sem_wait(semaphore);
if (rc) {
  perror("sem_wait");
  return 1;
}
sem_close metodu
Örnek vermem lazım.

sem_unlink metodu
sem_open ile açılan bir yapı sem_unlink ile kaldırılır.

sem_open ile kullanılan dosya ismi
Buradaki soruda açıklandığı gibi  sem_open ile verilen dosya ismi sistemde tekil olmalı. POSIX standardı isimin şeklini ve uzunluğunu açık bırakmış. Ancak soruda da açıklandığı gibi en portable isimler "/semaphore_tekil_ismi" şeklinde olanlar.

dosya sistemi
Bu semaphore türü dosya sistemini kullanıyor. Örneğin Linux'ta /dev/shm dizinine, Solaris'te ise /tmp dizinine erişim hakkı olmalı.

2. Unnamed Semaphore
İsimsiz semaphore yapısı için sem_init(), sem_destroy() metodları kullanılıyor. Bu konu ile ilgili sem_overview başlıklı yazı biraz daha açıklama sunuyor.

sem_init metodu
Metodun imzası şöyle
int sem_init(sem_t *sem, int pshared, unsigned int value); 
Açıklaması şöyle.
  • sem points to a semaphore object to initialize
  • pshared is a flag indicating whether or not the semaphore should be shared with fork()ed processes. LinuxThreads does not currently support shared semaphores
  • value is an initial value to set the semaphore to
Value değeri kullanıma hazır kaç tane kaynak olduğunu gösterir. Örneğin kuyrukta işlemeye hazır bir nesne varsa bu değer 1 ile başlatılır.
Örnek
Şöyle yaparız.
 sem_init(&finish, 0, 0);

a. fork ile kullanılan sem_init
Semaphore hep bir pointer olarak tutuluyor.
sem_t *mySemaphore
sem_init() metoduna geçilen pshared parametresi, bu isimsiz semaphore nesnesinin uygulamalar arasında paylaşılıp paylaşılamayacağını gösteriyor.
Paylaşılan bir semaphore şöyle açılır. Bu örnek fork yapan uygulamalar için uygundur.
sem_t* semp = (sem_t*)mmap(0, sizeof(sem_t),PROT_READ|PROT_WRITE,
                           MAP_ANONYMOUS|MAP_SHARED,0,0);
if ((void*)semp == MAP_FAILED) { perror("mmap");  exit(EX_OSERR); } 

sem_init(semp, 1 /*shared*/, 0 /*value*/);
b. Thread'ler Arası kullanılan sem_init
Bu semaphore pointer olarak tutulmak zorunda değil çünkü mmap yapmak gerekmez. Bu yüzden ikinci parametre 0 geçilir. Aşağıdaki örnekte kullanıma hazır 1 kaynak ile başlanıyor.
sem_t sem;
sem_init(&sem, 0, 1);
Kullanacak hiç kaynak yoksa şöyle yaparız.
sem_init(&sem, 0, 0);
3. Tüm Semaphore'lar İçin Metodlar
Named veya Unnamed tüm semaphore ile kullanılabilecek metodlar ise aşağıda

Semaphore ile ilgili bazı notlar şunlar:
sem_t değişkeni başka bir değişkene kopyalanamaz.

semp_post metodu
Şöyle yaparız. Semaphore nesnesinin değerini 1 artırır. Örneğin kuyruğa işlemek için yeni bir nesne eklenince bu metodu çağırırız.
sem_post(&sem);
sem_wait metodu
Şöyle yaparız. Örneğin kuyrukta işlemek için yeni bir nesne var mı kontrolünü yapmak için bu metodu çağırırız.
sem_wait(&sem);
semaphore'un değeri sıfır ise çağıran thread'i bloke eder.Ancak burada dikkat edilmesi gereken nokta, sem_wait EINTR ile bölünebilir. Bu durumda metodun döndürdüğü değere ve hata koduna bakmak lazım. Örnek:
/* Just wait for the semaphore */
do {
  result = (0 == sem_wait(&self->semaphore));
  if (!result) {
    result = errno;
  }
} while (EINTR == result);
Benim asıl ilgincime giden Qt ile gelen QSemaphore sınıfının her iki metodu da kullanmaması. Bu sınıf kaynak kodundan da görüldüğü gibi semaphore işlevini yerine getirmek için basit bir mutex ve sayaç kullanıyor.

sem_trywait
semaphore'un değeri sıfır ise -1 döner ve errno değişkeninin değerini EAGAIN olarak atar.

sem_destroy metodu
semaphore üzerinde bekleyen thread varsa, bu işlemin sonucu tanımsızdır (undefined ). Şöyle yaparız
sem_destroy(&sem);

sem_getvalue
semaphore'un değerini döndürür. Bu metod ile belirtilen semaphore üzerinde bloke olmuş kaç thread var öğrenilebilir. Anladığım kadarıyla POSIX bu metodun ya 0 ya da eksi bir sayı dönmesine izin veriyor. 0 dönerse işletim sistemi bu çağrıyı desteklemiyor anlamına geliyor. Eksi bir sayı dönerse mutlak değeri alınarak kullanılmalı ve bloke olan thread sayısını belirtiyor. Ancak bu çağrı sadece bir anlık görüntü (snapshot) döndürdüğü için pratikte ne işe yarar ben de anlamadım.

Bir semaphore mutex'in yerini alabilir mi ?

Alamaz
  1. Semaphore'un üst sınırı vardır. Mutex recursive ise n-defa da aynı kişi tarafından lock edilse çalışır ancak semaphore'un üst sınırı vardır vardır ve sadece sınırlı kez lock edilebilir. Yani kişi kendi kendini dead-lock haline sokabilir.
  2. Counting Semaphores, Binary Semaphores and Mutexes explained başlıklı yazıdan da görülebileceği gibi semaphore'un sahibi yoktur. Semaphore lock etmiş herhangi bir thread tarafından da unlock edilebilir. Mutex ise sadece lock etmiş thread tarafından unlock edilebilir.
Ayrıca Producer/Consumer kuyruklarında semaphore Producer şöyle kodlanır. Yani her zaman mutex kullanılır.
while (1) {
  pthread_mutex_lock(&mutex);  InsertAtHead(...);
  pthread_mutex_unlock(&mutex);

  sem_post(&semaphore);
 
}
Consumer şöyle kodlanır.
while (1) {
  sem_wait(&semaphore);
  
  pthread_mutex_lock(&mutex);
  PopTail();
  pthread_mutex_unlock(&mutex);
  
}

Bir binary semaphore mutex'e nerede tercih edilmeli ?
Where to use binary semaphore when mutex are available? yazısında da anlatıldığı gibi binary semaphore iki uygulama arasında bekle/devam et (wait/notify) tarzı iletişim için kullanılıyorsa mutex'ten daha kullanışlı.

Semaphore ve Bekleme Listesi
wait queues vs semaphores in linux başlıklı soruda Linux üzerinde de aynı mutex'te olduğu gibi bir bekleme listesi olduğu yazılı.

Hiç yorum yok:

Yorum Gönder