JET Academy

Semafor nədir?

Semafor (Semaphore) — paralel və konkurrent proqramlaşdırmada bir neçə proses və ya thread arasında sinxronizasiya və qarşılıqlı istisna (mutual exclusion) təmin edən, counter əsaslı işləyən sinxronizasiya primitividir. Semafor paylaşılan resurslara girişi idarə edir və məhdud sayda prosesə eyni anda resursa giriş icazəsi verir. İki əsas əməliyyatı var: wait (P, down) — counter azaldır və lazımsa gözləyir; signal (V, up) — counter artırır və gözləyən prosesi oyandırır. Semafor 1965-ci ildə Edsger Dijkstra tərəfindən ixtira edilmişdir.

Semafor nədir?

Semafor qeyri-mənfi tam ədəd counter dəyişənindən və onu idarə edən iki atomic əməliyyatdan ibarətdir. Counter resursa eyni anda neçə prosesin giriş edə biləcəyini göstərir. Semafor mutex-dan daha ümumi mexanizmdir — mutex yalnız bir prosesə icazə verir (binary), semafor isə N prosesə icazə verə bilər (counting).

Semafor növləri:

1. Binary Semaphore (İkili Semafor): Counter 0 və ya 1 dəyəri ala bilər. Mutex-ə ekvivalentdir və kritik bölgəyə (critical section) yalnız bir prosesin girişini təmin edir. Qarşılıqlı istisna (mutual exclusion) üçün istifadə olunur.

2. Counting Semaphore (Sayıcı Semafor): Counter 0-dan N-ə qədər dəyər ala bilər. N sayda eyni növ resursa (məsələn, database connection pool-da 10 connection, printer queue-da 5 printer) paralel girişi idarə edir.

Semaforun işləmə prinsipi

Semafor iki əsas əməliyyat təklif edir və hər iki əməliyyat atomic (bölünməz, kəsilməz) olmalıdır:

wait() / P() / down() əməliyyatı:

wait(S):

S = S - 1

if S < 0:

block process (proses gözləmə növbəsinə əlavə olunur)

Proses resursa giriş etmək istəyəndə wait() çağırır. Counter azalır. Əgər counter mənfi olarsa (resurs mövcud deyil), proses block olur və gözləyir.

signal() / V() / up() əməliyyatı:

signal(S):

S = S + 1

if S <= 0:

wake up one blocked process (gözləyən proseslərdən birini oyat)

Proses resursu buraxdıqda signal() çağırır. Counter artır. Əgər gözləyən proseslər varsa (S ≤ 0), onlardan biri oyadılır və işə davam edir.

Nümunə ssenari (3 printer ilə print queue):

  • Semafor initial value = 3 (3 printer mövcud)
  • Proses A: wait() çağırır → S = 2, printer alır, çap edir
  • Proses B: wait() çağırır → S = 1, printer alır
  • Proses C: wait() çağırır → S = 0, printer alır
  • Proses D: wait() çağırır → S = -1, printer yoxdur, D block olur və gözləyir
  • Proses A: signal() çağırır (çapı bitdi) → S = 0, printer buraxır, D oyanır və printer alır

Kod nümunələri

POSIX (C/Linux):

#include <semaphore.h>

#include <pthread.h>


sem_t semaphore;


// Initialize semaphore

sem_init(&semaphore, 0, 3); // 0 = thread-shared, 3 = initial value


void* worker(void* arg) {

sem_wait(&semaphore); // P operation, counter azalır

// Critical section - resurs istifadə olunur

printf("Thread %ld using resource\n", (long)arg);

sleep(2); // Resurs istifadəsi simulyasiyası

sem_post(&semaphore); // V operation, counter artır, resurs buraxılır

return NULL;

}


int main() {

pthread_t threads[5];

for (long i = 0; i < 5; i++) {

pthread_create(&threads[i], NULL, worker, (void*)i);

}

for (int i = 0; i < 5; i++) {

pthread_join(threads[i], NULL);

}

sem_destroy(&semaphore);

return 0;

}

Python (threading.Semaphore):

import threading

import time


# 3 resursa icazə verən semafor

semaphore = threading.Semaphore(3)


def worker(worker_id):

print(f"Worker {worker_id} waiting for resource...")

semaphore.acquire() # wait()

print(f"Worker {worker_id} acquired resource")

time.sleep(2) # Resurs istifadəsi

print(f"Worker {worker_id} releasing resource")

semaphore.release() # signal()


# 5 thread yaradırıq, lakin yalnız 3-ü eyni anda işləyə bilər

threads = []

for i in range(5):

t = threading.Thread(target=worker, args=(i,))

threads.append(t)

t.start()


for t in threads:

t.join()

Java:

import java.util.concurrent.Semaphore;


public class SemaphoreExample {

private static Semaphore semaphore = new Semaphore(3); // 3 permit

static class Worker extends Thread {

private int id;

Worker(int id) {

this.id = id;

}

public void run() {

try {

System.out.println("Worker " + id + " waiting...");

semaphore.acquire(); // wait()

System.out.println("Worker " + id + " acquired resource");

Thread.sleep(2000);

System.out.println("Worker " + id + " releasing resource");

semaphore.release(); // signal()

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public static void main(String[] args) {

for (int i = 0; i < 5; i++) {

new Worker(i).start();

}

}

}

Klassik semafor problemləri

1. Producer-Consumer Problem (İstehsalçı-İstehlakçı)

Məhdud ölçülü buffer-də producer məlumat yazır, consumer oxuyur.

3 semafor istifadə olunur:

  • empty: Boş slot sayı (initial = BUFFER_SIZE)
  • full: Dolu slot sayı (initial = 0)
  • mutex: Buffer-ə eksklüziv giriş üçün (initial = 1)

sem_t empty, full, mutex;

int buffer[BUFFER_SIZE];

int in = 0, out = 0;


void init() {

sem_init(&empty, 0, BUFFER_SIZE);

sem_init(&full, 0, 0);

sem_init(&mutex, 0, 1);

}


void* producer(void* arg) {

while (1) {

int item = produce_item();

sem_wait(&empty); // Boş slot gözlə

sem_wait(&mutex); // Buffer lock et

buffer[in] = item;

in = (in + 1) % BUFFER_SIZE;

sem_post(&mutex); // Buffer unlock

sem_post(&full); // Dolu slot signal

}

}


void* consumer(void* arg) {

while (1) {

sem_wait(&full); // Dolu slot gözlə

sem_wait(&mutex); // Buffer lock et

int item = buffer[out];

out = (out + 1) % BUFFER_SIZE;

sem_post(&mutex); // Buffer unlock

sem_post(&empty); // Boş slot signal

consume_item(item);

}

}

2. Readers-Writers Problem (Oxuyucu-Yazıcı)

Bir neçə reader eyni anda oxuya bilər, lakin writer eksklüziv giriş tələb edir.

Həll (reader priority):

sem_t mutex, write_lock;

int read_count = 0;


void init() {

sem_init(&mutex, 0, 1); // read_count müdafiəsi üçün

sem_init(&write_lock, 0, 1); // yazma lock

}


void* reader(void* arg) {

while (1) {

sem_wait(&mutex);

read_count++;

if (read_count == 1) {

sem_wait(&write_lock); // İlk reader writer-i block edir

}

sem_post(&mutex);

// Oxuma əməliyyatı

read_data();

sem_wait(&mutex);

read_count--;

if (read_count == 0) {

sem_post(&write_lock); // Son reader writer-ə icazə verir

}

sem_post(&mutex);

}

}


void* writer(void* arg) {

while (1) {

sem_wait(&write_lock);

// Yazma əməliyyatı (eksklüziv)

write_data();

sem_post(&write_lock);

}

}

3. Dining Philosophers Problem (Nahar edən filosoflar)

5 filosof dairəvi masa ətrafında. Hər filosofun arasında 1 çəngəl. Nahar etmək üçün 2 çəngəl lazım. Deadlock və starvation problemi.

Həll strategiyaları:

  • Maksimum 4 filosof eyni anda masa ətrafına otura bilər (semafor = 4)
  • Asimmetrik həll: Tək nömrəli filosoflar sol, cüt nömrəlilər sağ çəngəldən başlayır
  • Waiter (ofisiant) həlli: Mərkəzi arbiter icazə verir

Üstünlüklər:

  • Çoxlu resursa paralel girişi idarə edir
  • Producer-consumer kimi sinxronizasiya problemlərini həll edir
  • Deadlock prevention üçün düzgün istifadə oluna bilər
  • OS səviyyəsində dəstəklənir (POSIX sem_t)
  • Process və thread arasında paylaşıla bilər

Dezavantajlar:

  • Səhv istifadə deadlock yarada bilər (məsələn, signal/wait ardıcıllığı səhv)
  • Priority inversion problemi (aşağı prioritetli proses semaforu tutur)
  • Test və debug çətindir (race condition-lar intermittent ola bilər)
  • Busy-waiting ola bilər (spinlock semaforlarında)
  • Counter overflow riski (çox signal() çağırılarsa)

Təhlükəsizlik və best practices

Həmişə pair et: Hər wait() üçün bir signal() olmalıdır. Exception handling diqqətlə — signal() mütləq çağırılmalıdır.

Deadlock-dan çəkin: Semaforları həmişə eyni sıra ilə əldə edin. Nested semaphore-lardan ehtiyatlı olun.

Timeout istifadə et: sem_timedwait() (POSIX) ilə sonsuz gözləmənin qarşısını al.

Counter overflow: Counter maksimum dəyəri yoxla, sərhəd qoy.

Test: Helgrind (Valgrind), ThreadSanitizer kimi thread safety analiz alətləri.

Sənədləşdirmə: Hansı semafor hansı resursu qoruyur, aydın yaz.

Nəticədə, semafor konkurrent proqramlaşdırmada güclü və çevik sinxronizasiya mexanizmidir. Counting semaphore məhdud resurslara (connection pool, thread pool, limited hardware) girişi idarə etmək üçün idealdır. Binary semaphore mutex kimi istifadə oluna bilər. Producer-consumer, readers-writers, dining philosophers kimi klassik problemlərin həlli semafor ilə mümkündür. Düzgün istifadə ilə thread-safe, effektiv və deadlock-free proqramlar yazmaq mümkündür, lakin səhv istifadə deadlock, race condition və priority inversion kimi problemlərə səbəb ola bilər. Semaforun başa düşülməsi və düzgün tətbiqi paralel proqramlaşdırmada fundamental bacarıqdır.

Tədris sahələrimiz barədə məlumat almaq üçün qeydiyyatdan keçin

Digər tədris sahələri