Il Sistema Operativo Programmazione Concorrente Tipologie di Reti e di Mezzi trasmissivi - Giorgio Porcu Il Sistema Operativo Programmazione Concorrente BOZZA: 19/03/2016 Giorgio Porcu www.thegiorgio.it
Sommario Linguaggi concorrenti Le istruzioni fork e join Linguaggio sequenziale Linguaggio concorrente Pseudolinguaggio concorrente Le istruzioni fork e join Fork e Join Grafo di Precedenza Pseudocodice concorrente Esempi Semplificazione delle precedenze
Linguaggi concorrenti
Linguaggio sequenziale Linguaggio di programmazione che supporta nativamente la sola esecuzione sequenziale delle istruzioni. Gran parte dei linguaggi strutturati, come il C, appartiene a questa categoria. Utilizzando un linguaggio sequenziale il programmatore non può dichiarare porzioni di codice eseguibili in parallelo.
Linguaggio concorrente Linguaggio di programmazione che supporta nativamente l’esecuzione concorrente delle istruzioni. Ne è un esempio il linguaggio Java, che la supporta a livello utente con opportuni costrutti per i thread. Utilizzando un linguaggio concorrente il programmatore dichiara porzioni di codice parallelizzabili. L’effettiva esecuzione parallela dipende comunque dalle caratteristiche HW e SW (politiche di Scheduling del SO) dell’elaboratore.
Programmazione concorrente Attività finalizzata alla creazione di algoritmi che supportino attività concorrenti. Utilizza: I Grafi di precedenza per descrivere quali porzioni di codice siano parallelizzabili Un linguaggio concorrente per definire esplicitamente, con opportuni costrutti/istruzioni, porzioni di codice parallelizzabili
Pseudolinguaggio concorrente Poiché i linguaggi concorrenti sono di difficile utilizzo, ci serviremo a scopo didattico dello pseudocodice di uno pseudolinguaggio concorrente: Simile al linguaggio C Che utilizzi opportune istruzioni per dichiarare inizio e fine delle attività parallelizzabili. In realtà tale linguaggio non esiste, ma potrebbe essere simulato con il C su piattaforma Linux grazie alle funzioni fork(), exit() e wait()
Pseudocodifica concorrente La pseudocodifica del linguaggio concorrente si baserà su una struttura tipo come la seguente: inizio A; // esegui blocco istruzioni A B; // esegui blocco istruzioni B Ris = Prova(); // richiama funzione Prova() fine Prova() // definisci funzione Prova() { C; // esegui blocco istruzioni C }
Le istruzioni fork e join
Fork e Join Fork e Join Istruzioni per codificare l’esecuzione parallela. Sono state teorizzate negli anni ‘60 e sono applicate in vari modi nei linguaggi concorrenti. Nel nostro pseudolinguaggio concorrente immagineremo siano presenti come funzioni di sistema: fork() int fork(nomefunzione) join() void join(variabile|intero)
fork (1/2) Fork Istruzione che esprime l’inizio di un esecuzione parallela. Corrisponde alla biforcazione nel Grafo di precedenza (o pluriforcazione). E’ espressa in pseudocodice come segue: A; // blocco A P1=fork(NodoC); B; // blocco B NodoC() { C; // blocco C } B A C fork
Integrazione del blocco C con fork Per esprimere correttamente la fork occorre: Fissare nel Grafo una sequenza standard di istruzioni Integrare i restanti blocchi di istruzioni con una fork A; P1=fork(NodoC); B; NodoC() { C; } Sequenza standard B A C fork Integrazione del blocco C con fork
join (1/2) Join Istruzione che esprime la fine di un esecuzione parallela. Corrisponde alla riunione in un unico flusso delle istruzioni nel Grafo di precedenza. In pseudocodice: B; C; join(P2); D; La join è seguita dal nome della variabile cui è assegnato il valore di ritorno della precedente fork (in questo caso P2) B D C join
join (2/2) L’istruzione join può essere espressa in due modi: B D C Seguita dal nome della variabile usato nella fork, come nel precedente esempio Seguita da un numero intero indicante quanti blocchi di istruzioni si riuniscono in quel punto B; B; C; C; join(P2); join(2); D; D; B D C join Numero intero Nome della variabile
Grafo di Precedenza Pseudocodice Una tipica modalità di costruzione di un algoritmo concorrente consiste in: Analizzare un Grafo di precedenza contenente esecuzione parallela di istruzioni Trasformarlo in un algoritmo in pseudocodice che utilizzi le istruzioni fork() e join()
Realizzare Pseudocodice con fork/join Realizzare Pseudocodice con fork e join Per realizzare lo Pseudocodice concorrente occorre analizzare il Grafo di precedenza e: Utilizzare una fork per ogni pluriforcazione e una join per ogni riunione di blocchi di codice. Può essere utile segnare con una linea sul grafo la presenza delle istruzioni Ricordarsi che ogni join è preceduta da una fork Tener presente che all’inizio e alla fine dell’algoritmo tutte le istruzioni sono sequenziali
Pseudocodice: Esempio 1 Consideriamo il seguente Grafo di precedenza e trasformiamolo in pseudocodice: inizio A; // blocco A P1=fork(NodoC); B; // blocco B join(P1); D; fine NodoC() { C; // blocco C } B A C D fork join
Semplificazione delle precedenze Prima di realizzare il pseudocodice parallelo è opportuno analizzare attentamente il Grafo: in alcune situazioni è possibile semplificarlo. La semplificazione consiste nell’eliminare le precedenze implicite nei triangoli tra nodi, come nel caso seguente: B B A A C C
Semplificazione delle precedenze In questo caso il nodo A ha precedenza diretta su C, ma anche il percorso AB ha precedenza su C. Poiché valgono le regole generali: Tutti i nodi del Grafo dovranno essere elaborati (rappresentano codice) Tutte le precedenze del Grafo vanno rispettate C dovrà comunque attendere l’esecuzione di B: elimino dunque il passaggio diretto AC B B A A C C