Mutua esclusione e concorrenza

Con questo semplice articolo, studiando il classico problema dei lettori/scrittori, analizziamo un modo per evitare corse critiche alle risorse nei nostri programmi

Ogni studente ha dovuto affrontare un esame, un esercitazione o un progetto in cui si parla del problema scrittori / lettori. Praticamente nel nostro sistema un insieme di risorse, limitate, che possono essere utilizzate da diversi processi. Nel momento in cui un processo utilizza una risorsa questa diventa momentaneamente non disponibile per tutti gli altri processi (ad esempio la scheda audio o una stampante).

Questa situazione porta di solito a problemi di concorrenza tra i vari processi, che talvolta può provocare un deadlock delle nostre applicazioni (il Thread A aspetta una risorsa dal Thread B e il Thread B aspetta una risorsa dal Thread A). Per evitare questi problemi il linguaggio Java offre dei meccanismi che permettono allo sviluppatore di sincronizzare i vari Thread.

Prima di tutto dobbiamo dire che possiamo inserire nella signature del nostro metodo la parola riservata synchronized per indicare alla virtual machine che un solo Thread alla volta potrà accedere a questo metodo. Vediamo quindi un esempio di utilizzo:

public synchronized void playAudio()  {
   //Codice per la riproduzione audio
}

Questo permette di bloccare i Thread e creare una mutua esclusione. Talvolta però questo non basta al nostro programma. Talvolta infatti dobbiamo accedere con diversi programmi ad una stessa risorsa. Il problema classico è appunto quello dei lettori e dei scrittori di una risorsa.

Quando dobbiamo leggere una risorsa la possiamo far leggere a tutti i processi, mentre quando dobbiamo scrivere sulla risorsa (ad esempio un file) soltanto un processo deve accederci. Per questo scopo possiamo utilizzare i metodi che troviamo dentro la classe Object wait e notify che appunto permettono rispettivamente di bloccare l’esecuzione di un Thread e di notificare ad un’altro Thread che non deve più aspettare. Vediamo ora come poter utilizzare questi metodi per creare un vero e proprio semaforo per la nostra applicazione

public class Semaforo {
	private int readers = 0;
	private boolean writing = false;

	public synchronized void lockRead() 
        throws InterruptedException {
		while (writing) 
			wait();
		++readers;
	}

	public synchronized void unlockRead() {
		--readers;
		if (readers == 0) 
			notify();
           // solo gli scrittori sono in attesa
	   // e uno soltanto deve essere svegliato
	}

	public synchronized void lockWrite() 
        throws InterruptedException {
		while (readers > 0 || writing) 
			wait();
		writing = true; 
	}

	public synchronized void unlockWrite() {
		writing = false;
		notifyAll(); 
               // sblocca uno o piu' lettori
               // o uno scrittore
	}
}

Con questa classe quindi possiamo gestire il flusso d’esecuzione di diversi lettori/scrittori. Quando un lettore dovrà accedere alla risorsa dovrà operare in questa maniera.

...
semaforo.lockRead();
//Codice per leggere la risorsa
semaforo.unlockRead();
...

Lo scrittore in maniera analoga dovrà seguire il seguente pattern

...
semaforo.lockWrite();
//Codice per scrivere sulla risorsa
semaforo.unlockWrite();
...

Questo che abbiamo visto può essere un metodo elegante per organizzare lo scheduling delle risorse per i nostri programmi. Chiaramente si possono pensare anche altre soluzioni se il nostro programma richiedesse dei requisiti più particolari

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.