[semafori] Multi-snake

La verifica consiste nella sincronizzazione di N_SNAKES thread, dove ciascun thread è uno snake rappresentato da un identificatore numerico tra 0 e N_SNAKES-1. Ogni snake implementa il seguente comportamento:

  • Inizialmente è fermo e rimane in attesa di una sequenza di caratteri.
  • Mano a mano che riceve i caratteri, il corpo dello snake si trasforma nella stringa ricevuta.
  • Quando la trasformazione è completa, lo snake si muove finché non incontra un altro snake al quale invia la stringa, un carattere alla volta.
  • Trasmettendo i caratteri, lo snake ritorna nella sua forma iniziale e si rimette in attesa.

Essenzialmente lo schema di uno snake è il seguente:

snake(id) {
    while(1) {
        for (i=0; i < SNAKE_LENGTH; i++) {
            body[i] = leggi_char(id); // legge il char
            fatta_lettura(id); // segnala che la lettura è avvenuta
        }

        // lo snake si muove finché possibile

        if (bloccato dallo snake s)
            for (i=0; i < SNAKE_LENGTH; i++) {
                invia_char(s,body[i]); // invia il char
                attendi_lettura(s); // attende che s abbia letto
            }
    }
}

I sorgenti della verifica sono disponibili qui. Dovete implementare le funzioni presenti nel file soluzione.c che viene incluso dal file multi-snake.c

Esempio

La situazione iniziale è la seguente:

+----------------------------------------+
|                        3333         1  |
|                                     1  |
|                                     1  |
|                                     1  |
|                0000                    |
|         2                              |
|         2                              |
|         2                              |
|         2                              |
|                                        |
|                                        |
|                                        |
|                                        |
|                                        |
|                                        |
|                    4444                |
|                                        |
|                                        |
|                                        |
|                                        |
+----------------------------------------+

Il thread principale invia la stringa LEAD allo snake 2 (scelto in maniera casuale):

+----------------------------------------+
|                        3333         1  |
|                                     1  |
|                                     1  |
|                                     1  |
|                0000                    |
|         L                              |
|         E                              |
|         A                              |
|         D                              |
|                                        |
|                                        |
|                                        |
|                                        |
|                                        |
|                                        |
|                    4444                |
|                                        |
|                                        |
|                                        |
|                                        |
+----------------------------------------+

Lo snake 2 si sposta e colpisce lo snake 4, al quale passa la stringa un carattere alla volta:

+----------------------------------------+
|                        3333         1  |
|                                     1  |
|                                     1  |
|                                     1  |
|                0000                    |
|                                        |
|                                        |
|                                        |
|                                        |
|                                        |
|                                        |
|                     D                  |
|                     A                  |
|                     2                  |
|                     2                  |
|                    LE44                |
|                                        |
|                                        |
|                                        |
|                                        |
+----------------------------------------+
Visualizza la soluzione

Soluzione

Ecco una possibile soluzione commentata della verifica:

/*
 * La verifica consiste nel sincronizzare "N_SNAKES" thread. Ogni thread è uno 
 * snake il cui corpo è costituito da cifre che rappresentano l'identificatore
 * del serpente. Lo schema che si vuole realizzare è il seguente:
 * - ogni snake è in attesa di una sequenza di caratteri che, quando ricevuti,
 *   modificano gradualmente il corpo del serpente;
 * - ricevuta la stringa, lo snake inzia a muoversi nello schema fino a che non
 *   si scontra con un altro serpente, al quale invia a sua volta la stringa;
 * - trasmessa la stringa, il serpente ritorna in attesa.
 * All'avvio del programma tutti i serpenti sono fermi e la stringa viene
 * inviata dal thread principale ad un serpente scelto in modo casuale.
 *
 * Il problema è sostanzialmente una istanza di produttore-consumatore con 
 * l'unica differenza che il test sulle celle vuote viene fatto dopo aver 
 * scritto e quindi i semafori "vuoto" sono inizializzati a 0 (vedi la 
 * descrizione sotto)
 * La soluzione realizzata utilizza i seguenti semafori:
 * - un array di char "buffer" di dimensione "N_SNAKES" che rappresenta N_SNAKES
 *   buffer ognuno di dimensione 1 char. buffer[id] viene usato per inviare un
 *   char allo snake id.
 * - un vettore di semafori "pieno" di dimensione "N_SNAKES" utilizzato dai vari
 *   serpenti per attendere che venga inviato loro un carattere. In particolare,
 *   il semaforo "pieno[id]" è verde se per lo snake di indice "id" è disponibile
 *   un carattere da leggere, rosso altrimenti. Il semaforo dice se buffer[id] è
 *   pieno. 
 * - un vettore di semafori "vuoto" di dimensione "N_SNAKES" utilizzato dai vari
 *   serpenti per attendere che il carattere inviato sia stato letto. In
 *   particolare, il semaforo "vuoto[id]" è verde se lo snake di indice "id" ha 
 *   letto il carattere inviato, rosso altrimenti. Il semaforo dice se buffer[id] è
 *   vuoto.
 * Notare che poiché attendi_lettura viene invocato per la prima volta dopo aver
 * inviato il carattere, i semafori vuoto[id] devono essere inizializzati a 0.
 */

 // variabili globali e semafori qui
sem_t vuoto[N_SNAKES],pieno[N_SNAKES]; // array di semafori vuoto e pieno
char buffer[N_SNAKES];                 // array di char per bufferizzare il carattere

// invocato all'inizio dal thread principale prima di creare gli snake
void init_sem() {
	int i;
	for (i=0;i<N_SNAKES;i++) {
		// i buffer sarebbero vuoti ma il semaforo viene testato solo dopo
		// l'invio del carattere quindi lo inizializziamo a 0 in modo che i
		// thread attendano correttamente la lettura del carattere
		sem_init(&vuoto[i],0,0); 
		// i buffer sono vuoti quindi inizializziamo i semafori pieno a 0
		sem_init(&pieno[i],0,0); 
	}
}

// invocato dal thread prima della terminazione del programma
void destroy_sem() {
	int i;
	for (i=0;i<N_SNAKES;i++) {
		sem_destroy(&vuoto[i]); // elimina i semafori vuoto
		sem_destroy(&pieno[i]); // elimina i semafori pieno
	}
}

// invia un carattere al thread id
void invia_char(int id, char c) {
	// i buffer sono inizialmente vuoti quindi è OK scriverci
	buffer[id] = c;			// salva il carattere nel buffer
	sem_post(&pieno[id]);   // notifica che il buffer è pieno e può essere letto
}

// il thread id legge un carattere (bloccante se il carattere non è stato inviato)
char leggi_char(int id) {
	sem_wait(&pieno[id]); 	// attende che il buffer sia pieno
	return buffer[id];    	// legge il carattere dal buffer e lo restituisce
}

// attende che il thread id abbia letto
void attendi_lettura(int id) {
	// questa wait nel produttore/consumatore viene fatta prima di scrivere.
	// In questo caso, invece, la facciamo dopo, di conseguenza il semaforo è stato
	// inizializzato a 0
	sem_wait(&vuoto[id]);	// attende che il buffer sia stato letto (vuoto)
}

// il thread id segnala che la lettura è avvenuta
void fatta_lettura(int id) {
	sem_post(&vuoto[id]);	// segnala che il buffer è stato letto (vuoto)
}