Semaforele
Semaforele sunt un alt instrument pe care platforma .NET îl oferă pentru gestionarea sincronizării. Semaforele permit limitarea numărului de thread-uri care au acces la anumite resurse. În .NET, semaforele sunt reprezentate de clasa Semaphore.
Pentru a crea un semafor, se folosește unul dintre constructorii clasei Semaphore:
- Semaphore(int initialCount, int maximumCount): parametrul initialCount setează numărul inițial de thread-uri, iar maximumCount - numărul maxim de thread-uri care au acces la resursele comune
- Semaphore(int initialCount, int maximumCount, string? name): în plus, setează numele semaforului
- Semaphore(int initialCount, int maximumCount, string? name, out bool createdNew): ultimul parametru - createdNew indică prin valoarea true că semaforul a fost creat cu succes. Dacă acest parametru este false, semaforul cu numele specificat deja există
Pentru a lucra cu thread-uri, clasa Semaphore are două metode principale:
- WaitOne(): așteaptă obținerea unui loc liber în semafor
- Release(): eliberează un loc în semafor
De exemplu, avem următoarea sarcină: există un număr de cititori care vin la bibliotecă de trei ori pe zi pentru a citi. Să presupunem că avem o limită de trei cititori simultan în bibliotecă. Această sarcină poate fi rezolvată foarte ușor cu ajutorul semafoarelor:
// pornim cinci thread-uri
for (int i = 1; i < 6; i++)
{
Reader reader = new Reader(i);
}
class Reader
{
// creăm semaforul
static Semaphore sem = new Semaphore(3, 3);
Thread myThread;
int count = 3; // numărul de citiri
public Reader(int i)
{
myThread = new Thread(Read);
myThread.Name = $"Cititor {i}";
myThread.Start();
}
public void Read()
{
while (count > 0)
{
sem.WaitOne(); // așteptăm eliberarea unui loc
Console.WriteLine($"{Thread.CurrentThread.Name} intră în bibliotecă");
Console.WriteLine($"{Thread.CurrentThread.Name} citește");
Thread.Sleep(1000);
Console.WriteLine($"{Thread.CurrentThread.Name} părăsește biblioteca");
sem.Release(); // eliberăm locul
count--;
Thread.Sleep(1000);
}
}
}
În acest program, cititorul este reprezentat de clasa Reader. Aceasta încapsulează toată funcționalitatea legată de thread-uri prin variabila Thread myThread.
Semaforul în sine este definit ca o variabilă statică sem:
static Semaphore sem = new Semaphore(3, 3);
Constructorul său primește doi parametri: primul specifică numărul de obiecte care inițial vor avea acces la semafor, iar al doilea specifică numărul maxim de obiecte care vor folosi semaforul. În acest caz, doar trei cititori pot fi simultan în bibliotecă, deci numărul maxim este 3.
Funcționalitatea principală este concentrată în metoda Read, care se execută în thread. La început, pentru a aștepta eliberarea unui loc în semafor, se folosește metoda sem.WaitOne():
sem.WaitOne(); // așteptăm eliberarea unui loc
După ce un loc se eliberează în semafor, thread-ul respectiv ocupă locul și începe să execute toate acțiunile următoare.
După terminarea citirii, eliberăm semaforul folosind metoda sem.Release():
sem.Release(); // eliberăm locul
Astfel, un loc se eliberează în semafor și este ocupat de un alt thread aflat în așteptare.
Exemplu de ieșire pe consolă:
Cititor 5 intră în bibliotecă
Cititor 5 citește
Cititor 4 intră în bibliotecă
Cititor 4 citește
Cititor 1 intră în bibliotecă
Cititor 1 citește
Cititor 5 părăsește biblioteca
Cititor 1 părăsește biblioteca
Cititor 4 părăsește biblioteca
Cititor 3 intră în bibliotecă
Cititor 3 citește
Cititor 2 intră în bibliotecă
Cititor 2 citește
Cititor 4 intră în bibliotecă
Cititor 3 părăsește biblioteca
Cititor 2 părăsește biblioteca
Cititor 5 intră în bibliotecă
Cititor 5 citește
Cititor 4 citește
Cititor 1 intră în bibliotecă
Cititor 1 citește
Cititor 5 părăsește biblioteca
Cititor 3 intră în bibliotecă
Cititor 3 citește
Cititor 4 părăsește biblioteca
Cititor 1 părăsește biblioteca
Cititor 2 intră în bibliotecă
Cititor 2 citește
Cititor 3 părăsește biblioteca
Cititor 5 intră în bibliotecă
Cititor 5 citește
Cititor 2 părăsește biblioteca
Cititor 1 intră în bibliotecă
Cititor 4 intră în bibliotecă
Cititor 1 citește
Cititor 4 citește
Cititor 5 părăsește biblioteca
Cititor 1 părăsește biblioteca
Cititor 4 părăsește biblioteca
Cititor 2 intră în bibliotecă
Cititor 3 intră în bibliotecă
Cititor 2 citește
Cititor 3 citește
Cititor 3 părăsește biblioteca
Cititor 2 părăsește biblioteca