Concorrenza e Sincronizzazione di Thread e Processi
Concorrenza e Sincronizzazione Obiettivi garantire la mutua esclusione nell’accesso a risorse condivise da parte di thread/processi concorrenti Più in generale, stabilire un coordinamento fra (sincronizzare) le azioni di thread/processi concorrenti
Mutua-esclusione Problemi da risolvere Vincoli Aggiornamenti perduti (lost update) Azione: aggiornamento concorrente di un dato da parte di più thread/processi Risultato: l’ultimo valore sovrascrive tutti gli altri Letture inconsistenti (inconsistent read) Azione: lettura di un dati che successivamente vengono modificati da altri thread/processi concorrenti Risultato: i calcoli basati sui dati non aggiornati producono risultati sbagliati Vincoli Garantire il massimo di attività concorrenti (liveness) Evitare lo stallo (deadlock) NB: Se l’accesso alle variabili condivise avviene solo in lettura (nessun thread cambia I valori) non ci sono mai problemi di mutua-esclusione
Esempio di inconsistenza a + 1 – 1 è sempre uguale a zero? Un caso pratico. Si supponga che… a sia una variabile condivisa da 200 thread 100 thread incrementano a 100 thread decrementano a …qual è il valore finale di a?
Un occhio al codice assembler Incremento di a load $R1, a add $R1, 1 store a, $R1 Decremento di a load $R1, a sub $R1, 1 Store a,$R1 100 thread per ogni sequenza
Possibile sequenza di esecuzione a vale inizialmente 0, comincia Thread 1 load $R1, a // legge il valore di a, $R1 = 0 add $R1, 1 // incremento, $R1 = 1 Preemption, cambio contesto, comincia Thread 2 sub $R1, 1 // decremento, $R1 = -1 store a, $R1 // scrive valore di a, a = -1 Preemption, cambio contesto, continua Thread 1 (NB: L’operazione 5 ha reso inconsistente la lettura 1) store a, $R1 // scrive il valore di a, a = 1 Fine, a vale 1 invece che 0 (NB: L’operazione 6 ha sovrascritto l’aggiornamento 5) Inconsistent read Lost update
Soluzione: sezioni critiche Una sezione critica viene eseguita senza cambi di contesto intermedi Incremento di a test_and_set lock load $R1, a add $R1, 1 store a, $R1 unset lock Decremento di a test_and_set lock load $R1, a sub $R1, 1 store a,$R1 unset lock
Mutua Esclusione in Windows: Oggetti Mutex Mutex = Mutual Exclusion object CreateMutex() ReleaseMutex() WaitForSingleObject() A basso livello, Windows usa un meccanismo hardware di sincronizzazione (sul modello test-and-set) per implementare un lock “primitivo” chiamato spinlock
Procedimento Si crea l’oggetto mutex uso CreateMutex() Si inizia la sezione critica, occupando l’oggetto mutex uso WaitForSingleObject() Alla fine della regione critica, si rilascia l’oggetto mutex uso ReleaseMutex()
CreateMutex Funzione: creazione di un nuovo oggetto Mutex HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName) lpMutexAttributes: attributi di sicurezza bInitialOwner: se true, il Mutex viene risulta già occupato dal processo/thread che lo crea lpName: nome simbolico dell’oggetto Restituisce lo handle del Mutex creato
WaitForSingleObject Funzione: test_and_set di un oggetto Mutex DWORD WaitForSingleObject( HANDLE hMutex, DWORD dwMilliseconds) hMutex: il mutex da occupare dwMilliseconds: timeout di attesa (eventualmente INFINITE) Restituisce l’indicazione del motivo della terminazione (es. Timeout)
ReleaseMutex Funzione: rilascio (unlock) di un Mutex precedentemente occupato BOOL ReleaseMutex(HANDLE hMutex) hMutex: il mutex da rilasciare Restituisce TRUE in caso di successo
Stati degli Handle e WaitForSingleObject Gli handle sono associati a “eventi di sincronizzazione”, in base ai quali possono transire fra due possibili stati: Signaled unsignaled [evento di sincronizzazione] WaitForSingleObject bloccata Handle signaled Handle unsignaled WaitForSingleObject eseguita
Handle e WaitForSingleObject Caso dei Mutex CreateMutex(…,FALSE,…) [evento di sincronizzazione] ReleaseMutex(…) WaitForSingleObject bloccata Handle Mutex signaled Handle Mutex unsignaled WaitForSingleObject eseguita
Handle e WaitForSingleObject Caso di Thread/Processi CreateProcess(…) CreateThread(…) [evento di sincronizzazione] [terminazione del thread/processo] WaitForSingleObject bloccata Handle thread signaled Handle thread unsignaled WaitForSingleObject eseguita
Il nome degli oggetti Perché è possibile dare un nome ad un oggetto come un Mutex? Dare un nome ad un oggetto significa dare visibilità globale all’oggetto Gli oggetti visibili globalmente sono accessibili da altri processi Procedimento generale Se in fase di creazione di un oggetto si specifica il nome di un oggetto che esiste già nel sistema, viene restituito lo Handle dell’oggetto esistente
Handle table dei processi e object manager globale Gestore unico degli oggetti: li crea, cancella, li contiene, ecc…. Processo A H.T. Object Manager Oggetto 1 Oggetto 2 Oggetto 3 Oggetto 4 Processo B H.T. Processo C H.T.
Condivisione dei Mutex Se in CreateMutex di specifica il nome (terzo par.) di un Mutex già esistente Viene restituito lo handle al Mutex esistente Il secondo parametro (occupazione iniziale del Mutex) viene ignorato
Coordinamento con Eventi Un evento è una segnalazione di un processo/thread (solitamente relativa a un cambiamento di stato) che può essere ricevuta da un altro processo/thread Il processo/thread che gestisce l’evento (e lo segnala) viene detto “observer” Il processo che riceve l’evento (e reagisce di conseguenza) viene detto “listener” CreateEvent() per creare un nuovo evento o connettersi ad un evento esistente SetEvent() per segnalare un evento WaitForSingleObject() per ascoltare un evento, bloccandosi in attesa che sia segnalato
Handle e WaitForSingleObject Caso degli Eventi CreateEvent(…) [evento di sincronizzazione] SetEvent(...) WaitForSingleObject bloccata Handle evento signaled Handle evento unsignaled WaitForSingleObject eseguita
CreateEvent Funzione: creazione di un nuovo oggetto evento o connessione ad uno esistente HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset BOOL bInitialState LPCTSTR lpName); lpEventAttributes: attributi di sicurezza bManualReset: gestione manuale del reset (vedere ResetEvent) bInitialOwner: se true, l’evento risulta già segnalato alla creazione lpName: nome simbolico dell’oggetto Restituisce lo handle dell’evento creato (o connesso)
SetEvent Funzione: segnalazione di un evento BOOL SetEvent(HANDLE hEvent) hEvent: l’evento da segnalare Restituisce TRUE in caso di successo