Semafor Ve Mutex Kavramları
Kaynak | Tarih | Versiyon | İşlem |
Zafer SATILMIŞ | 2020-01-13 | 0.1 | Döküman oluşturuldu |
2.1 İsimlendirilmiş POSIX Semaforlar 3
2.2 İsimlendirilmiş POSIX Semaforlar Örneği 7
2.3 İsimlendirilmemiş POSIX Semaforlar 12
2.4 İsimlendirilmemiş POSIX Semaforlar Örneği 13
Bu belgenin amacı Semafor ve Mutex kavramlarını açıklayıp aralarındaki farkları ve ortak özellikleri okuyucuya sunmaktır. Anlatımı pekiştirmek için POSIX fonksiyonları kullanılarak C dilinde örnekler verilmiştir.
Semaforlar process ve thread sekronizasyonu için kullanılır. Mutex’ler ikilik semaforlardır. Mutex’ler aynı proses ve thread’leri tarafından kullanılır. Fakat semaforlar farklı prosesler tarafından da kullanılabilir.
Sekronizasyonu açmak gerekirse bellek paylaşımı, kritik kod bloğunun kullanımı, donanım elemanını sırayla kullanma gibi örnekler verebiliriz. İster embedded sistemlerde isterse de daha üst cihazlarda çalışılsın yukarıda verilen örneklerin gereksinimi ele alınmalıdır. Hiç bir donanım parçası kullanılmasa dahi en basitinden kritik kod bloğunu koruma durumu oluşacaktır. (ufak 1, 2 dosyadan oluşan projelerde göremeyebilirsiniz.)
İki çeşit semafor bulunmaktadır. Birincisi System V diğeri de POSIX semafordur. Biz bu belgede POSIX semafor ele alacağız. POSIX semaforlar 2.6 glibc versiyonundan sonra kullanılabilirdir ve kullanımı System V göre daha basittir.
POSIX semaforlar isimli ve isimsiz (named and unamed semaphores) semaforlar olmak üzere ikiye ayrılırlar. Her ne kadar kulağa aralarında sadede isimlendirme ya da isim kabul etme gibi bir fark varmış görülsede kullanım biçimi ve yetenekleri farklıdır. Fakat isimlendirilmiş semaforlar isimsiz semaforların kullanıldığı yerde kullanılabilir iken tersi mümkün değildir.
İsimlendirilmiş semaforların oluşumu, tıpkı dosya işlemlerinde gerekli dosya ismi gibi kullanıcının belirlediği bir isim üzerinden oluşur. Belirlenen isim ile oluşturulan semafor diğer işlem birimleri(process) tarafından da bilindiğinde aynı semafora ulaşabilir. Yani kısaca bu tipteki semaforlar, tanımlanan bir isim üzerinden farklı process'ler arasında kullanılabilirler.
İsimlendirimiş semaforların kullanımı için gerekli POSIX fonksiyonları şu şekildedir:
sem_open() ile semafor oluşturulur veya kullanım için açılır.
sem_post() ile semaforun değeri artırılırken sem_wait() ile semaforun azaltılır
sem_getvalue() ile o anki değeri öğrenilir
sem_close() ile semafore erişim iptal edilir
sem_unlink() ile semafor tamamen silinmek üzere işaretlenir, semaforu kullanan tüm process'ler sem_close() yaptıktan sonra silme gerçekleşir.
#include <fcntl.h> |
#include <semaphore.h> |
Bu fonksiyon semafor değerini bir arttırır. Semafor değerinin bir arttırılma işlemine semafor için V operasyonu denir. Semafor değerinin bir arttırılması bir başka process ya da thread ilgili alana erişim izni vererek sıradaki process ya da thread çalışmasını ya da uyanmasını sağlar. Kısaca kapının anahtarını açıp içeri başkasının girmesine izin vermek diyebiliriz.
Eğer işlem başarılı ise 0, hatalı ise -1 değerini döner.
#include <semaphore.h> |
Bu fonksiyon semafor değerini bir azaltmak için kullanılır. Eğer semafor değeri sıfır değilse azaltma işlemi gerçekleşir. Eğer semafor değeri sıfır ise semafor değeri bir başkası tarafından bir arttırılana kadar bekler ve tekrar kendisi bir azaltır. Semafor değerinin azaltılması işlemine P operasyonu denir.
Eğer işlem başarılı ise 0, hatalı ise -1 değerini döner. Hata durumunda ayrıca errno da ilgilli hata numarası yüklenir.
#include <semaphore.h> |
Bu fonksiyon tıpkı sem_wait() fonksiyonu gibi çalışır. Fakat semafor değeri sıfır iken sem_wait() gibi beklemez(unblock) ve hemen geri döner ve errno değerine EAGAIN değerini yükler.(#define EAGAIN 11 /* Try again */)
#include <semaphore.h> |
sem_wait () gibi P operasyonu için kullanılır. Fakat bu fonksiyon sem_wait()’ den farklı olarak zaman aşımı parametresi içerir. Hatırlar iseniz sem_wait() işlemini yapana kadar bekliyordu. sem_timedwait() eğer semafor değeri sıfır ise belirtilen zaman aşımı içinde semafor değeri sıfır değerinin üzerine çıkar ise işlemini başarı ile yapar. Aksi durumda zaman aşımı gerçekleşir ve errno değerine ETIMEOUT yükler ve işlemin başarısız olduğunu belirterek geri döner.
struct timespec |
#include <semaphore.h> |
sem_getvalue() ile sem adresinin (pointer) gösterdiği semaforun tam sayı değeri ile geri döner. Başarı durumunda 0, hata durumunda -1 döner.
#include <semaphore.h> |
Belirtilen isimdeki semaforu kaldırır.
Bu başlık altında yukarıdaki senaryoyu gerçekleştiren bir örnek yazacağız. Amacımız bir bellek alanını birden farzla üretici ve tek bir kullanıcı arasında kullanıma sunmak.
$ gcc semafor_test.c -o semafor_test -lpthread
#include <sys/types.h> |
Yukarıdaki örnek uygulamada for döngüsü ile oluşturulan 10 adet thread aynı buffer alanına sprintf (buf [j], "Thread %d: %d\n", my_id, ++count); satırı ile yazmaktadır. Spooler fonksiyonu içinde ise buffer içindeki yazılar kullanılmaktadır.
Görüldüğü gibi threadlar arası uyumu semafor ile sağlamış olduk. Ee biz bunu mutex ile de yapabilirdik. Evet doğru, mutex ile bu işlemi yapabilirdik, anlatımın bitmesi için biraz daha sabır. Belgenin başındaki şu cümleleri hatırlayalım; “Mutex’ler ikilik semaforlardır. Mutex’ler aynı proses ve thread’leri tarafından kullanılır.” Bu nedenle threadler arası (aynı process) semafor kullanılması boşa kaynak harcamaktır. Bu sebeple yukarıdaki örnek sadece konuyu açıklamak amacıyla yazılmıştır.(tek dosyada derlenip okuyucunun anlamasını kolaylaştırmak amaçlanmıştır). İsimlendirilmiş semaforlar threadler arası değil processler arası uyumu sağlamak amacıyla kullanılır. Belirtilen isimdeki semaforu sem_open() ile açan her process diğer processler ile uyumlu çalışmayı sağlayabilir.
Konuyu daha iyi anlamak için yukarıdaki örneğin producer ve spooler kısımlarını ayırarak ayrı bir uygulamaya çevirebilirsiniz.
İsimsiz semaforlara “Memory Based Samaphores” denir. Semaforların ismi yoktur ve ortak bellek alanında saklanır. Yukarıdaki örnekte de görüldüğü gibi aynı process içinde threadler arası uyumu sağlamak için kullanılır.
#include <semaphore.h> |
sem_init(), ilgili semaforun kurulmasını sağlar. Fonksiyonun 2. parametresi olan pshared değeri 0 olduğu takdirde, semafor sadece aynı sürecin thread'leri içerisinde kullanılabilir. Bu nedenle shared memory kullanımına gerek kalmadan, global bir değişkende veya heap üzerinde ayrılan bir alanda tutulabilir.
pshared değeri 0'dan farklı olursa, semafor farklı süreçler arasında kullanılabilir. Bu durumda 1. parametredeki adres, shared memory üzerindeki bir yeri göstermelidir. Bu fonksiyonun aynı semafor için birden fazla çağrılması durumunda sistem kararlı davranamaz. Bu nedenle, her semaforun sadece bir defa kurulması garanti edilmelidir.
#include <semaphore.h> |
İlgili semaforun sonlandırılmasını sağlar. Semafor sonlandırılmadan, tutulduğu bellek bölgesi free edilmemelidir.
İsimlendirilmiş semaforda verdiğimiz örneği bu başlık altında tekrar ele alalım. Fakat ilk olarak birden fazla thread aynı isimlendirilmemiş semaforu kullanabilmesi için sem_init() fonksiyonun sem_t * argümanı için vereceğimiz değişken global olmasına dikkat etmemiz gerekmektedir.
#include <sys/types.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <errno.h> #include <pthread.h> #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #include <unistd.h> // Buffer data structures #define MAX_BUFFERS 10 char buf [MAX_BUFFERS] [100]; int buffer_index; int buffer_print_index; sem_t mutex_sem, buffer_count_sem, spool_signal_sem; void *producer (void *arg); void *spooler (void *arg); int main (int argc, char **argv) { pthread_t tid_producer [10], tid_spooler; int i, r; // initialization buffer_index = buffer_print_index = 0; // mutual exclusion semaphore, mutex_sem with an initial value 1. if (sem_init (&mutex_sem, 0, 1) == -1) { perror ("sem_init"); exit (1); } // counting semaphore, indicating the number of available buffers. Initial value = MAX_BUFFERS if (sem_init (&buffer_count_sem, 0, MAX_BUFFERS) == -1) { perror ("sem_init"); exit (1); } // counting semaphore, indicating the number of strings to be printed. Initial value = 0 if (sem_init (&spool_signal_sem, 0, 0) == -1) { perror ("sem_init"); exit (1); } // Create spooler if ((r = pthread_create (&tid_spooler, NULL, spooler, NULL)) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // Create 10 producer threads int thread_no [10]; for (i = 0; i < 10; i++) { thread_no [i] = i; if ((r = pthread_create (&tid_producer [i], NULL, producer, (void *) &thread_no [i])) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } } // Wait for producers to terminate for (i = 0; i < 10; i++) { if ((r = pthread_join (tid_producer [i], NULL)) == -1) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } }
// No more strings to print? Wait for spool_signal_sem to become 0 int semval; while (1) { if (sem_getvalue (&spool_signal_sem, &semval)== -1) perror ("sem_getvalue");
if (!semval) break;
sleep (1); } // terminate spooler if ((r = pthread_cancel (tid_spooler)) != 0) { fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1); } // Destroy semaphores if (sem_destroy (&mutex_sem) == -1) { perror ("sem_destroy"); exit (1); } if (sem_destroy (&mutex_sem) == -1) { perror ("sem_destroy"); exit (1); } if (sem_destroy (&mutex_sem) == -1) { perror ("sem_destroy"); exit (1); } exit (0); } // producer: produce strings for printing // There might be multiple producer threads void *producer (void *arg) { // Create 10 strings and terminate int i; int my_id = *((int *) arg); int count = 0; for (i = 0; i < 10; i++) { // get a buffer: P (buffer_count_sem); if (sem_wait (&buffer_count_sem) == -1) { perror ("sem_wait: buffer_count_sem"); exit (1); } /* There might be multiple producers. We must ensure that only one producer uses buffer_index at a time. */ // P (mutex_sem); if (sem_wait (&mutex_sem) == -1) { perror ("sem_wait: mutex_sem"); exit (1); } // Critical section int j = buffer_index; buffer_index++;
if (buffer_index == MAX_BUFFERS) buffer_index = 0; // Release mutex sem: V (mutex_sem) if (sem_post (&mutex_sem) == -1) { perror ("sem_post: mutex_sem"); exit (1); } // Produce a string sprintf (buf [j], "Thread %d: %d\n", my_id, ++count); // Tell spooler that there is a string to print: V (spool_signal_sem); if (sem_post (&spool_signal_sem) == -1) { perror ("sem_post: spool_signal_sem"); exit (1); } // Take a nap sleep (1); } } // There is only one spooler thread void *spooler (void *arg) { while (1) { // forever // Is there a string to print? P (spool_signal_sem); if (sem_wait (&spool_signal_sem) == -1) { perror ("sem_wait: spool_signal_sem"); exit (1); } printf ("%s", buf [buffer_print_index]); /* Since there is only one thread (spooler) using the buffer_print_index, mutex semaphore is not necessary */ buffer_print_index++; if (buffer_print_index == MAX_BUFFERS) buffer_print_index = 0; /* Contents of one buffer has been printed. One more buffer is available for use by producers. Release buffer: V (buffer_count_sem); */ if (sem_post (&buffer_count_sem) == -1) { perror ("sem_post: buffer_count_sem"); exit (1); } } } |