SPIEGAZIONE BUFFER CIRCOLARE (RINGBUFFER) ========================================== Un BUFFER CIRCOLARE è un array dove quando arriviamo alla fine, torniamo all'inizio. Visualizzazione: ┌─────────────────┐ │ Buffer[20] │ │ [0][1][2]...[19]│ └─────────────────┘ ↑ ↑ INIZIO FINE SENZA BUFFER CIRCOLARE (ARRAY NORMALE): ======================================== int buffer[20]; int posW = 0; // Posizione per scrivere Scrittura: buffer[0] = 10 buffer[1] = 20 buffer[2] = 30 ... buffer[19] = 200 buffer[20] = ??? ← ERRORE! Array out of bounds! CON BUFFER CIRCOLARE: ==================== int buffer[20]; int posW = 0; buffer[0] = 10 buffer[1] = 20 ... buffer[19] = 200 posW = (posW + 1) % 20 ← OPERAZIONE MAGICA! posW = 0 ← TORNA AL PRINCIPIO! buffer[0] = 210 ← Sovrascrivi il primo elemento Questa è la chiave: % (modulo) che fa "tornare indietro" COME FUNZIONA IL MODULO: ======================= % (resto della divisione) 0 % 20 = 0 1 % 20 = 1 2 % 20 = 2 ... 19 % 20 = 19 20 % 20 = 0 ← Torna a 0! 21 % 20 = 1 22 % 20 = 2 ... 39 % 20 = 19 40 % 20 = 0 ← Torna a 0 di nuovo! Vedi il pattern? È un ciclo infinito che torna sempre a 0! ESEMPIO PRATICO - PROD/CONS: ============================= int buffer[5]; // Piccolo buffer per visualizzare bene int posW = 0; // Posizione productor (write) int posR = 0; // Posizione consumer (read) CICLO 1: -------- prod scrive: buffer[0] = 10 posW = (0 + 1) % 5 = 1 buffer[1] = 20 posW = (1 + 1) % 5 = 2 buffer[2] = 30 posW = (2 + 1) % 5 = 3 cons legge: x = buffer[0] = 10 posR = (0 + 1) % 5 = 1 x = buffer[1] = 20 posR = (1 + 1) % 5 = 2 CICLO 2: -------- prod continua da posW=3: buffer[3] = 40 posW = (3 + 1) % 5 = 4 buffer[4] = 50 posW = (4 + 1) % 5 = 0 ← TORNA AL PRINCIPIO! cons continua da posR=2: x = buffer[2] = 30 posR = (2 + 1) % 5 = 3 prod sovrascrive (circolarmente): buffer[0] = 60 ← Sovrascritto il vecchio 10! posW = (0 + 1) % 5 = 1 cons legge ancora: x = buffer[3] = 40 posR = (3 + 1) % 5 = 4 x = buffer[4] = 50 posR = (4 + 1) % 5 = 0 ← Anche lui torna al principio! x = buffer[0] = 60 ← Legge il nuovo valore VISUALIZZAZIONE CIRCOLARE: ========================== Buffer di 5 elementi, veduto come cerchio: PRODUTTORE ↓ 0 ← 1 ← 2 ← 3 ← 4 ← (torna a 0) ↓ CONSUMER prod@3, cons@1: ✓ Entrambi hanno spazio/dati prod@0, cons@0: ❌ ERRORE! Prod e cons sullo stesso punto = conflitto! VANTAGGI DEL BUFFER CIRCOLARE: ============================== 1. Non spreca spazio → Riusa lo stesso array 2. Veloce → Usa modulo per restare in bounds 3. Perfetto per producer-consumer → Prod scrive, cons legge NEL TUO CODICE (prod/cons): =========================== int buffer[20]; int posW = 0; int posR = 0; pthread_mutex_lock(&mutex); buffer[posW] = x; ← Scrivi in posW posW = (posW + 1) % 20; ← Va al prossimo (o torna a 0) pthread_mutex_unlock(&mutex); pthread_mutex_lock(&mutex); x = buffer[posR]; ← Leggi da posR posR = (posR + 1) % 20; ← Va al prossimo (o torna a 0) pthread_mutex_unlock(&mutex); SEMAFORI PROTEGGONO LA LOGICA: ============================== sem_t SP; // Semaforo spazi liberi sem_t SC; // Semaforo dati disponibili sem_init(&SP, 0, 20); // 20 spazi liberi inizialmente sem_init(&SC, 0, 0); // 0 dati disponibili inizialmente prod: sem_wait(&SP); ← "C'è spazio?" Se no, aspetta // scrivi nel buffer sem_post(&SC); ← "Ho messo un dato disponibile!" cons: sem_wait(&SC); ← "C'è un dato?" Se no, aspetta // leggi dal buffer sem_post(&SP); ← "Ho liberato uno spazio!" MUTEX PROTEGGE I DATI: ====================== pthread_mutex_lock(&mutex); buffer[posW] = x; ← Solo uno per volta può scrivere posW = ... pthread_mutex_unlock(&mutex); LA "DANZA" COMPLETA: ==================== Stato iniziale: buffer[20], posW=0, posR=0 SP=20 (spazi liberi), SC=0 (dati disponibili) PROD Thread: 1. sem_wait(&SP) ← SP diventa 19 2. pthread_mutex_lock(&mutex) ← Prende il lucchetto 3. buffer[0] = 10; posW = 1 ← Modifica buffer 4. pthread_mutex_unlock(&mutex) ← Rilascia il lucchetto 5. sem_post(&SC) ← SC diventa 1 CONS Thread: 1. sem_wait(&SC) ← SC diventa 0 (era 1) 2. pthread_mutex_lock(&mutex) ← Prende il lucchetto 3. x = buffer[0]; posR = 1 ← Legge buffer 4. pthread_mutex_unlock(&mutex) ← Rilascia il lucchetto 5. sem_post(&SP) ← SP diventa 20 CHIAVE IMPORTANTE: =================== Quando % 20 = 0: posW è arrivato a 20 (20) % 20 = 0 ← TORNA A 0! Ora scrivi in buffer[0] di nuovo, sovraścrivendo il vecchio dato ⚠️ ATTENZIONE! Devi assicurarti che il consumer ha già letto buffer[0] prima che il producer lo sovrascritti! Questo è quello che garantiscono i SEMAFORI: - SP (spazi liberi) assicura che non sovrascritti dati non letti - SC (dati disponibili) assicura che non leggi spazi vuoti CONCLUSIONE: ============ Buffer Circolare = Array che si "avvolge" su se stesso Vantaggi: ✓ Efficiente (riusa spazio) ✓ Semplice (usa modulo) ✓ Perfetto per producer-consumer Protetto da: ✓ SEMAFORI (sincronizzazione prod/cons) ✓ MUTEX (accesso esclusivo ai dati) È come una cinghia trasportatrice che torna indietro quando finisce! pthread_mutex_lock(&mutex); // Mette il dato nel buffer alla posizione di scrittura buffer[posW] = x; printf("PRODOTTO: %d in posizione %d\n", x, posW); // Avanza la posizione di scrittura (torna a 0 se raggiunge 20) posW = (posW + 1) % 20; // ← QUESTO È IL "CIRCOLARE"! pthread_mutex_unlock(&mutex); // 4. AVVISA i consumatori che c'è un dato disponibile sem_post(&SC); // Incrementa i dati disponibili Sleep(100); // Delay } return NULL; } void *consumatore(void *arg) { while(1) { // 1. ASPETTA CHE CI SIA ALMENO UN DATO nel buffer // SC = semaforo dei dati disponibili // Se buffer è vuoto (SC = 0), si blocca qui sem_wait(&SC); // 2. ENTRA NEL BUFFER (sezione critica) pthread_mutex_lock(&mutex); // Legge il dato dal buffer alla posizione di lettura int x = buffer[posR]; printf("CONSUMATO: %d dalla posizione %d\n", x, posR); // Avanza la posizione di lettura (torna a 0 se raggiunge 20) posR = (posR + 1) % 20; // ← ANCHE QUESTO! pthread_mutex_unlock(&mutex); // 3. AVVISA i produttori che c'è uno spazio libero sem_post(&SP); // Incrementa gli spazi liberi Sleep(rand() % 1000 + 500); // Delay random } return NULL; } int main() { srand(time(NULL)); // INIZIALIZZO I SEMAFORI sem_init(&SP, 0, 20); // 20 spazi liberi all'inizio sem_init(&SC, 0, 0); // 0 dati disponibili all'inizio pthread_mutex_init(&mutex, NULL); pthread_t prod_, cons_, cons2_; // Crea 1 produttore e 2 consumatori pthread_create(&prod_, NULL, produttore, NULL); pthread_create(&cons_, NULL, consumatore, NULL); pthread_create(&cons2_, NULL, consumatore, NULL); // Aspetta (in realtà non termina mai) pthread_join(prod_, NULL); pthread_join(cons_, NULL); pthread_join(cons2_, NULL); return 0; } COME FUNZIONA IL CIRCOLARE: =========================== Immagina: buffer[20] e posW = 0, posR = 0 STEP 1: Producer mette dato buffer[0] = 50 posW = (0 + 1) % 20 = 1 STEP 2: Producer mette altro dato buffer[1] = 75 posW = (1 + 1) % 20 = 2 ...continua... STEP 19: Producer mette 19° dato buffer[19] = 30 posW = (19 + 1) % 20 = 0 ← TORNA A ZERO! STEP 20: Producer mette 20° dato buffer[0] = 45 ← SOVRASCRIVE il primo (se consumer lo ha già letto) posW = (0 + 1) % 20 = 1 VISUALIZZAZIONE: ================= STATO INIZIALE: buffer: [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _] posW = 0, posR = 0 (stessa posizione, buffer VUOTO) DOPO CHE PRODUCER HA MESSO 5 DATI: buffer: [50, 75, 30, 45, 20, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _] posW = 5, posR = 0 CONSUMER ha letto 3: buffer: [50, 75, 30, 45, 20, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _] posW = 5, posR = 3 (spazio tra posR e posW = dati disponibili) DOPO CHE PRODUCER HA RIEMPITO TUTTO: buffer: [50, 75, 30, 45, 20, 10, 5, 99, 15, 88, 42, 7, 33, 11, 66, 22, 44, 18, 55, 39] posW = 0 (torna a zero), posR = 3 CONSUMER continua a leggere da posR = 3 VANTAGGI DEL BUFFER CIRCOLARE: =============================== ✓ NON hai bisogno di allocare/deallocare memoria ✓ Riutilizzi il SAME ARRAY sempre ✓ Efficiente in memoria ✓ Perfetto per streaming di dati (video, audio, etc.) ✓ Non sprechi posizioni: quando posW raggiunge la fine, torna all'inizio ANALOGY: ======== Immagina una CORSIA BUFFO (ad una macchina): - Producer = Macchina che deposita pacchi - Consumer = Macchina che ritira pacchi - Buffer = Strada circolare con 20 posizioni La macchina che deposita gira in tondo, mettendo pacchi. La macchina che ritira gira dietro di lei, prendendo pacchi. Quando una raggiunge la fine, torna all'inizio! 🔄