Sei sulla pagina 1di 32

Multithreading e programmazione funzionale in Cpp0X C++11

Vincenzo La Spesa 10 aprile 2012

Indice
1 Introduzione: Il C++ non ` ancora morto e 1.1 Lo standard C++0X - C++11 . . . . . . . . . . . . . . . . . . . . . . . . 2 il C++ e le lambda 2.1 Il caso pi` semplice, le funzioni void . . . . . . . . u 2.2 Le funzioni che usano e restituiscono parametri . . 2.3 Le funzioni lambda e la visibilit` . . . . . . . . . . a 2.4 Funzioni che ricevono funzioni e funzioni anonime . 2.5 Qualche esempio funzionale ( che funziona) . . . . 3 4 4 5 5 6 7 7

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

3 Il C++ e il Multithreading 10 3.1 Allochiamo il nostro primo thread . . . . . . . . . . . . . . . . . . . . . . 10 3.2 Thread e condivisione dei dati . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.3 Allocare un Thread senza creare un oggetto . . . . . . . . . . . . . . . . . 13 4 Sincronizzazione dei Thread e gestione delle risorse con i Mutex 14 4.1 I Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4.2 Mutex ed eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 5 Uso avanzato dei Mutex 5.1 Timed mutex e la prenotazione con timeout massimo . . . . . . . . . . . . 5.2 Accesso alle funzioni interne del mutex . . . . . . . . . . . . . . . . . . . . 5.2.1 Prenotazione multipla . . . . . . . . . . . . . . . . . . . . . . . . . 18 19 19 20

6 Il problema dei 5 loso 21 6.1 Vari approcci ingenui . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 6.2 Una soluzione funzionante . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 7 Le Condition, implementazione delle code di attesa 24 7.1 Le Condition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 7.2 Ma le cose non sono cos` semplici... . . . . . . . . . . . . . . . . . . . . . . 25 1

8 Il problema del barbiere addormentato 26 8.1 Il barbiere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 8.2 Il cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 8.3 Codice completo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 A Ambiente di sviluppo e problemi di A.1 GNU Gcc - g++ . . . . . . . A.1.1 MinGw . . . . . . . . A.1.2 Multithreading . . . . A.2 Microsoft Visual Studio . . . Riferimenti bibliograci v1.0.0 http://www.thedarshan.com/ VincenzoLaSpesa@gmail.com Questopera ` stata rilasciata sotto la licenza Creative Commons Attribuzione-Non e commerciale-Condividi allo stesso modo 2.5 Italia. Per leggere una copia della licenza visita il sito web http://creativecommons.org/licenses/by-nc-sa/2.5/it/ o spedisci una lettera a Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. compilazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 31 31 31 31 31

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

A Questo documento ` stato prodotto e impaginato con L TEX e

1 Introduzione: Il C++ non ` ancora morto e


Uno dei linguaggi che ho visto pi` maltrattare allUniversit` e di conseguenza poi da u a molti (sedicenti e/o futuri) ingegneri del software che hanno fatto il grave errore di accettare acriticamente le illazioni dei professori ` il C++ (un mio professore ne aveva e quasi terrore... ) Michele Sciabarr` anni fa scrisse in un suo post 1 veramente geniale: a C++ Livello di complessit`: ESAGERATO. Per impararlo ci metti met` a a della tua vita. E poi impieghi laltra met` a convincere gli altri a impararlo, a perch devi giusticare a te stesso PERCHE hai perduto met` della tua e a vita. Credi di fare una cosa, e invece ne fai unaltra. La stessa cosa, in un caso signica una cosa e in un altro signica unaltra. Senza contare che la stessa cosa per un compilatore signica una cosa, per un altro c` un bug che la fa e diventare unaltra, e in un altro ancora non ` supportata. E non abbiamo e ancora considerato i template. Ora, Sciabarr` scherzava ma cpp si dicono molte cose cattive...la maggior parte delle a quali secondo me derivano da un confronto inappropriato e stupido con Java, si dice prima di tutto che

Il C++ ` troppo complicato! nessuno lo conosce per intero! e Il cpp non ` complicato, ` vasto. Lidea che un linguaggio vada imparato e usato e e per intero ` tipica dei linguaggi di nuova generazione come il Java, ma non ` e e assolutamente una cosa necessaria in genere. Il cpp parte dal principio che se ` possibile fare una cosa il linguaggio deve permetterlo, se poi i 4 tipi di cast o e lereditariet` multipla ti fanno orrore o paura...non usarli! Resta il fatto che siano a utili e comodi per fare workaround, e i workaround servono ( nel mondo reale almeno) e anche fare cose orribili come modicare le variabili dichiarate costanti pu` tornare utile. o un altra idea ancora pi` stupida ma comunque abbastanza in voga e che: u La libreria del C++ ` scarna! e Confondere la libreria di un linguaggio con il linguaggio ` veramente stupido! anche e dare per assunto che un linguaggio necessiti di una libreria intrinseca lo `, peraltro e le stl e la libreria standard orono un set di funzionalit` che copre tutte le necessit` a a di base, e per il resto se ne trovano a palate di librerie pronte. E adesso veniamo al vero motivo per cui sto scrivendo:
1

http://michele.sciabarra.com/page/NonUsateQuelLinguaggio

1.1 Lo standard C++0X - C++11


Dicevo che c++ non ` morto e si continua ad evolvere, il suo ultimo standard non ` e e quello del 1998 che tutti conoscono ( o dicono di conoscere) e nemmeno quello del 2003. Nel 2008 il C++ Standards Committee ha sviluppato un nuovo standard chiamato provvisoriamente Cpp0X, la bozza ` stata presentata con il nome di N3126 il 21 agosto e 2010. Il 25 marzo 2011 lISO vota la bozza nale (targata N3290) che viene contrassegnata come FDIS (Final Draft International Standard). Il 1o settembre 2011 viene pubblicata la versione nale del C++11 da parte di ISO e IEC. La nuova versione introduce molte nuove funzionalit`, sia nel linguaggio che nella a sua libreria standard tra cui le pi` importanti sono il supporto alla programmazione u funzionale ( delegate e lambda function) e al multithreading con linclusione nelle stl di un interfaccia completa ai pthread ( e chi abbia mai lavorato con quei cosi sa quanto sia prolissa attualmente la loro gestione). Attualmente il nuovo standard ` supportato dalle ultime versioni di Microsoft Vie sual Studio e dagli ultimi Gcc utilizzando dei ag di compilazione, le procedure di compilazione sono esposte alla ne del tutorial.

2 il C++ e le lambda
Un interessante funzione introdotta nello standard C++0X sono le lambda Le funzioni lambda sono tipiche dei linguaggi che applicano il paradigma della programmazione funzionale ( completamente o in maniera ibrida) e permettono di trattare le funzioni come se fossero oggetti, potendole quindi denire in qualunque punto del codice e assegnare a una variabile. Vediamo un esempio di come questo approccio sia stato implementato nel cpp

2.1 Il caso pi` semplice, le funzioni void u


Listing 1: le funzioni void - void
#include <i o s t r e a m> using namespace s t d ; auto lambda = [ ] ( ) { cout << Questa e una lambda << e n d l ; }; i nt main ( ) { auto lambda2 = [ ] ( ) { cout << Una lambda puo e s s e r e d e f i n i t a anche d e n t r o un a l t r a f u n z i o n e << e n d l ; }; cout << H e l l o wo r ld ! << e n d l ; lambda ( ) ; lambda2 ( ) ; return 0 ; }

come si vede dal codice abbiamo denito due funzioni lambda assegnandole a variabili, adesso vediamo come comportarsi per denire funzioni che usano o producono valori (queste qui erano void > void)

2.2 Le funzioni che usano e restituiscono parametri


Listing 2: Le funzioni che usano e restituiscono parametri
i nt main ( ) { auto somma = [ ] ( i nt a , i nt b ) { return ( a+b ) ; }; auto p r o d o t t o = [ ] ( i nt a , i nt b ) > i nt { return ( a+b ) ; }; cout << 2+2 f a << somma ( 2 , 2 ) << e n d l ; cout << 22 f a << p r o d o t t o ( 2 , 2 ) << e n d l ; return 0 ; }

la lista dei parametri in entrata viene dichiarata tra le parentesi tonde, il tipo del parametro in uscita pu` essere denito esplicitamente con > come nel caso di prodottoo o lasciato implicito come nel caso di somma e in questo caso viene dedotto dal compilatore A questo punto vi starete chiedendo a che servano le parentesi quadre suppongo

2.3 Le funzioni lambda e la visibilit` a


La programmazione a oggetti ci ha abituato al concetto di visibilit` di una variabile, per a esempio una funzione denita dentro una classe accede agli oggetti della classe, ma una lambda che pu` essere denita in qualunque punto del codice a cosa pu` accedere? la o o risposta `: assolutamente a nulla se si vuole fare in modo che qualcosa venga visto e dallinterno di una lambda bisogna passare il suo reference tra le quadre, vediamo un esempio Listing 3: un codice che non funziona
i nt main ( ) { i nt c ; auto somma = [ ] ( i nt a , i nt b ) { c=a+b ; }; ...

la compilazione del pezzo di codice precedente ci dar` un error: c is not captured a per renderlo visibile dobbiamo fare questo Listing 4: Il modo giusto di procedere
i nt main ( ) { i nt c ; auto somma=[&c ] ( i nt a , i nt b ) { c=a+b ; }; somma ( 2 , 2 ) ; cout << 2+2 f a << c << e n d l ; return 0 ; }

quindi passando un puntatore a this a una lambda rendiamo visibile lintero oggetto. ` addirittura possibile passare lintero contesto usando [&] e [=] dove la prima passa e per reference e la seconda per valore

2.4 Funzioni che ricevono funzioni e funzioni anonime


Una caratteristica fondamentale della programmazione funzionale ` la possibilit` di pase a sare funzioni ad altre funzioni, in questo caso per` la sintassi del cpp ` un po sporca e o e siamo costretti ad usare la libreria functional perch non ` interamente possibile farlo e e direttamente usando solo il linguaggio e le sue keyword, ci servir` la classe generica a f unction < tipouscita(tipiingresso) > nellesempio possiamo notare che ` anche possibile denire una funzione e passarla e senza assegnarla a una variabile, la funzione resta quindi anonima Possiamo inoltre vedere come sia possibile modicare il comportamento di una fuzione dal suo esterno passandogli un altra funzione, infatti nel primo caso scrivisomma scrive sullo stdout mentre nel secondo su le Listing 5: funzioni che ricevono funzioni
#include <i o s t r e a m > #include <f s t r e a m> #include <f u n c t i o n a l > using namespace s t d ; i nt main ( ) { i nt c ; char f i l e n a m e= d a t i . t x t ; auto scr iviso mma =[&]( i nt a , i nt b , const f u n c t i o n <void ( i nt)>& w r i t e r ) { c=a+b ; writer ( c ); }; auto s c r i t t o r e =[=]( i nt x ) { ofstream myfile ; m y f i l e . open ( f i l e n a m e ) ; m y f i l e << x << \n ; myfile . close ( ) ; }; scr iviso mma ( 2 , 2 , s c r i t t o r e ) ; scr iviso mma ( 2 , 2 , [ ] ( i nt x ) { cout << x << e n d l ; } ) ; return 0 ; }

2.5 Qualche esempio funzionale ( che funziona)


Adesso cerchiamo di inventarci qualcosa che sia comodo da realizzare con la programmazione funzionale e scomodo con quella procedurale... Abbiamo visto il caso precedente di una funzione che si aspetta unaltra funzione da utilizzare per scrivere i dati. Un

altro uso molto diuso delle lambda ci permette di modicare il comportamento di un algoritmo passandogli un altro algoritmo, un semplice esempio ` una generica funzione di e ordinamento a cui passiamo una funzione di confronto, questo ci permette sia di ordinare ogetti di tipi non conosciuti dalla funzione e non ordinabili a priori sia di modicare le relazioni di ordine.

Listing 6: un esempio completo


#include #include #include #include #include #include #include <i o s t r e a m > <f s t r e a m> <f u n c t i o n a l > <s t d i o . h> < s t d l i b . h> <s t r i n g . h> <time . h>

using namespace s t d ; void s o r t ( void x [ ] , i nt n , const f u n c t i o n <i nt ( void , void)>& co mpa r a to r ) { i nt i , j ; void app ; for ( i =1; i <n ; i ++) { app = x [ i ] ; j = i 1; while ( j >=0 && c o mpa r a to r ( x [ j ] , app) >0) { x [ j +1] = x [ j ] ; j ; } x [ j +1] = app ; } return ; } i nt main ( ) { s r a n d ( time ( 0 ) ) ; i nt c ; char c o s i [ ] = { p o l l o d i gomma , ammaccabanane , c o s o , c a n n u c c i a s b i r u l a } ; auto o r d i n a s t r = [ ] ( void a , void b)>i nt { return s t r l e n ( ( const char ) a) s t r l e n ( ( const char ) b ) ; }; auto d i s o r d i n a s t r = [ ] ( void a , void b)>i nt { return rand ()%6 3;}; s o r t ( ( void ) c o s i , 4 , o r d i n a s t r ) ; for ( i nt n=0;n<4;n++)co ut << c o s i [ n ] << e n d l ; cout<<endl<<e n d l ; s o r t ( ( void ) c o s i , 4 , d i s o r d i n a s t r ) ; for ( i nt n=0;n<4;n++)co ut << c o s i [ n ] << e n d l ; return 0 ; } }

3 Il C++ e il Multithreading
Unaltra funzionalit` molto interessante introdotta nel C++0X ` il supporto nativo al a e multithreading. Esistevano gi` librerie che si occupavano di multithreading in cpp ma adesso ` intea e grato nel linguaggio e nella sua libreria standard anche se il backend che eettivamente alloca i thread ` lonnipresente libreria pthread. e

3.1 Allochiamo il nostro primo thread


Un thread ` un qualcosa che viene eseguito in parallelo, e quindi si crea attraverso una e funzione che ` quello che deve fare, non ` necessario creare una classe intorno al thread e e come si farebbe in Java, basta passare al costruttore di thread la funzione che deve eseguire il thread costruito o qualcosa di equivalente. Cpp ` un linguaggio marcatamente OOP e infatti non ` necessario passargli una e e procedura, basta qualunque ogetto supporti loperatore (), possiamo anche creare una classe che denisce questo operatore e passarne un istanza.

10

Listing 7: Vari modi per allocare un thread


#include <i o s t r e a m > #include <thr ea d> #include <c s t d l i b > using namespace s t d ; void c o s a d a f a r e ( ) { cout<< Sono una p r o c e d u r a ! <<e n d l ; } c l a s s Dummy { public : void operator ( ) ( ) { for ( i nt a =0;a <5;a++){ cout<< Sono un o g g e t t o , e q u i n d i p i u f i g o d i una p r o c e d u r a ! << a <<e n d l ; ` u s l e e p ( 1000 ( rand ( ) % 900 + 100 ) ) ; } } }; void u c c i d i m i ( ) { for ( i nt a =0;a <50;a++){ cout<< I o p a r l o tr o ppo << a <<e n d l ; u s l e e p ( 1000 ( rand ( ) % 900 + 100 ) ) ; } } i nt main ( ) { Dummy w; s t d : : t h r e a d t 1 (w ) ; std : : thread t2 ( co sa da fa r e ) ; std : : thread t3 ( uccidimi ) ; t1 . j o i n ( ) ; t2 . j o i n ( ) ; t 3 . deta ch ( ) ; };

E interessante notare che quando si chiama un thread su un oggetto, come nel caso di std::thread t1(w), non gli viene passato il vero oggetto ma una sua copia, ma di questo ne parliamo dopo. Le join servono ad aspettare che i thread niscono prima di terminare il programma,

11

infatti i thread sono concettualmente gli del processo padre ( il fatto che lo siano veramente dipende dallimplementazione) e se terminasse il processo padre i gli verrebbero terminati di conseguenza facendo generare a linux un allarmante messaggio di warning, per terminare un thread prima che abbia nito il suo lavoro si usa detach

3.2 Thread e condivisione dei dati


Uno dei vantaggi dei thread rispetto ai processi ` che condividono lo spazio di memoria e tra di loro, tutto quello che ` nello spazio di indirizzamento del processo padre pu` essere e o passato ai gli. Quindi risulta molto conveniente piazzare dentro un oggetto la procedura del thread e i dati in cui dovr` lavorare ma c` un inconveniente: come dicevamo sopra quando si a e chiama un thread su un oggetto, come nel caso di std::thread t1(w), non gli viene passato il vero oggetto ma una sua copia , questo ` irrilevante nel caso di e oggetti che wrappano solo una procedura ma pu` essere una fonte di bug subdoli nel caso o essi contengano dati infatti modicando loggetto non modichiamo niente allinterno del thread perch sono oggetti diversi e se vogliamo evitare questo comportamento dobbiamo forzare un passaggio via reference e non abbiamo modo di farlo col linguaggio, dobbiamo adarci alla magia nera delle stl,la soluzione `: std::thread t1(std::ref(dw)); e Vediamo un esempio

12

Listing 8: Oggetti clonati e oggetti linkati


#include #include #include #include <i o s t r e a m > <thr ea d> <c s t d l i b > <s t r i n g >

using namespace s t d ; c l a s s Dummy { public : string testo ; s t r i n g nome ; void operator ( ) ( ) { for ( i nt a =0;a <15;a++){ cout<<nome << d i c e : << t e s t o << << a <<e n d l ; u s l e e p ( 1000 ( rand ( ) % 900 + 100 ) ) ; } } }; i nt main ( ) { Dummy w, w2 ; w. t e s t o=ammaccabanane ; w. nome= t h r e a d 1 ; w2=w ; w2 . nome= t h r e a d 2 ; s t d : : t h r e a d t 1 (w ) ; s t d : : t h r e a d t 2 ( s t d : : r e f (w2 ) ) ; sleep (2); w. t e s t o= Yikes ! ; w2 . t e s t o= Yikes ! ; t1 . j o i n ( ) ; t2 . j o i n ( ) ; };

Eseguendolo noterete che il comportamento del primo thread resta invariato, mentre possiamo agire sul secondo modicando i suoi dati dallesterno.

3.3 Allocare un Thread senza creare un oggetto


Malgrado la soluzione di un oggetto di wrapper pu` tornare utile il passare dei parametri o alla procedura chiamante, il che ci permette anche di non usare completamente un oggetto e di passare tutti i dati su cui deve lavorare il thread alla funzione che ne

13

costituisce il main, semplicemente accodiamo i parametri da passare alla funzione e ci adiamo alle stl per la determinazione corretta del tipo 2 Listing 9: E se volessi passare parametri alla procedura e magari non usare un oggetto?
#include #include #include #include <i o s t r e a m > <thr ea d> <c s t d l i b > <s t r i n g >

using namespace s t d ; void stampa ( s t r i n g co sa , s t r i n g a l t r a c o s a ) { for ( i nt a =0;a <10;a++){ cout<< c o s a << a l t r a c o s a << e n d l ; sleep (1); } } i nt main ( ) { s t d : : t h r e a d t 1 ( stampa , b l a b l a b l a , yada yada yada ) ; t1 . j o i n ( ) ; }; };

4 Sincronizzazione dei Thread e gestione delle risorse con i Mutex


Come accennato nel capitolo precedente un applicazione che sfrutta il multithreading ` e costituita da un insieme di processi sequenziali cooperanti, tutti in esecuzione asincrona che possono condividere dati e risorse. Laccesso concorrente ai dati o alle risorse pu` provocare incoerenze nei dati o sio tuazioni di deadlock nel caso di risorse che prevedono accesso esclusivo, in questo post cercher` di analizzare gli strumenti che C++0X ore per la sincronizzazione. o Allinterno di un processo si possono separare le sezioni in cui lavora su dati interni da quelle in cui si accede a dati o risorse condivise, queste ultime vengono dette sezioni critiche e sono quelle di cui si deve curare la sincronizzazione. Non tratter` in questo articolo le soluzioni toriche full-software per la gestione delle o s e sezioni critiche (algoritmi di Dekker, di Peterson, algoritmo del banchiere e del panettiere di Lamport, semafori in spinlock) ma tratter` luso di funzioni messe a disposizione dalla o libreria che ci permettono di assicurare la mutua esclusione tra processi attraverso lo scheduler.
la sintassi per gli argomenti multipli esisteva gi`, ma ` stata migliorata, se siete curiosi leggete qui a e http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2242.pdf
2

14

Per prima cosa cerchiamo di denire cosa deve succedere su una sezione critica: una volta individuata e minimizzata una sezione critica dobbiamo fare in modo che 1. sia vericata la mutua esclusione ossia che soltanto un processo per volta possa trovarsi nella sezione critica 2. si eviti lattesa indenita da parte di un processo che tenta di accedere a una sezione critica occupata 3. un processo che si trova fuori dalla sezione critica non deve poter interferire con laccesso alla sezione critica dei processi in attesa

4.1 I Mutex
Lo strumento fondamentale per la sincronizzazione oerto dalle stl ` il Mutex, che ` e e la crasi di mutual exlusion e serve appunto ad evitare che pi` processi del necessario u possano accedere ad una sezione critica. Esistono 4 tipi di mutex: 1. semplice ( std::mutex ) 2. semplice con timeout ( std::timed mutex ) 3. ricorsivo ( std::recursive mutex ) 4. ricorsivo con timeout ( std::recursive timed mutex ) un mutex ` semplice se il suo stato ` binario ( libero/occupato ) e e ` invece ricorsivo se permette pi` accessi prima di essere occupato e u un mutex ` timed se ` possibile fare in modo che si liberi da solo dopo un certo e e tempo fondamentalmente un mutex dispone di almeno 3 funzioni: void lock(); che permette di occuparlo o di attenderlo se ` gi` occupato e a void unlock(); che permette di liberarlo bool try lock(); che permette occuparlo se ` possibile senza per` rimanere in attesa e o vediamo come usarli, per prima cosa costruiamo un programma che necessiti di essere sincronizzato

15

Listing 10: un codice che non funziona


#include #include #include #include <i o s t r e a m > <thr ea d> <c s t d l i b > <s t r i n g >

using namespace s t d ; void stampa ( s t r i n g t e s t o ) { for ( i nt a =0;a<t e s t o . l e n g t h ( ) ; a++){ cout << t e s t o [ a ] ; f l u s h ( co ut ) ; u s l e e p ( 100 ( rand ( ) % 900 + 100 ) ) ; } cout << e n d l ; } c l a s s Worker { public : string testo ; Worker ( s t r i n g t ) { t e s t o=t ; } void operator ( ) ( ) { stampa ( t e s t o ) ; } }; i nt main ( ) { t h r e a d t 1 ( Worker ( Quanto l e g n o r o d e r e b b e un r o d i t o r e s e un r o d i t o r e p o t e s s e r o d e r e i l l e g n o ? ) ) ; t h r e a d t 2 ( Worker ( How much wood c o u l d a woodchuck chuck i f a woodchuck c o u l d chuck wood? ) ) ; t1 . j o i n ( ) ; t2 . j o i n ( ) ; };

il codice ci far` ottenere qualcosa del tipo: a QHowu anmtuoc hl egwonodo rcooudelrd ebbae wuoond rocdhiutcokr cehuc ske uinf a r odiwtooroed chpuoctkes csouled rchoduecrek i l woloegdno??

16

La sincronizzazione si eettua creando un mutex globale e condividendolo tra i thread che vanno sincronizzati e racchiudendo la sezione critica dentro una lock e una unlock Listing 11: La versione sincronizzata del codice precedente
... c l a s s Worker { public : string testo ; mutex m; Worker ( s t r i n g t , mutex mtx ) { m =mtx ; t e s t o=t ; } void operator ( ) ( ) { m >l o c k ( ) ; stampa ( t e s t o ) ; m >u n l o c k ( ) ; } }; i nt main ( ) { mutex m new mutex ( ) ; = t h r e a d t 1 ( Worker ( Quanto l e g n o r o d e r e b b e un r o d i t o r e s e un r o d i t o r e p o t e s s e r o d e r e i l l e g n o ? ,m) ) ; t h r e a d t 2 ( Worker ( How much wood c o u l d a woodchuck chuck i f a woodchuck c o u l d chuck wood? ,m) ) ; t1 . j o i n ( ) ; t2 . j o i n ( ) ; };

4.2 Mutex ed eccezioni


Malgrado laccesso diretto alle funzioni del mutex sia possibile ` un modo pericoloso di e procedere. Infatti non gestisce le eccezioni, se un thread genera un errore allinterno della zona critica il thread viene terminato ma il mutex non viene sbloccato, quindi il modo corretto di gestire una sezione critica sarebbe usare un trycatch in questo modo:

17

Listing 12: Accesso diretto al mutex


void operator ( ) ( ) { m >l o c k ( ) ; try { stampa ( t e s t o ) ; m >u n l o c k ( ) ; } catch ( . . . ) { m >u n l o c k ( ) ; cout << Yikes ! << e n d l ; } }

Un modo pi` compatto di usare in maniera sicura i mutex si pu` ottenere usando u o gli ogetti lock guard delle stl, che permettono anche di rilasciare il mutex in maniera automatica quando si esce dal blocco in cui sono denite ( lo sbloccano nel loro distruttore per essere precisi), il codice precedente si pu` riscrivere in questo modo: o Listing 13: Uso di una guard
void operator ( ) ( ) { l o c k g u a r d <mutex> l o c k ( m) ; stampa ( t e s t o ) ; }

Precisiamo una cosa per`... o Il fatto che la guard blocchi il mutex non implica che lintero programma non venga terminato...infatti dopo aver sbloccato il mutex propaga leccezione generata a livello superiore e se non c` un catch a livello superiore questo equivale alla e terminazione del processo padre Quindi se ci limitiamo a guardare questi snippet lesempio con la gestione manuale funziona meglio di quello con la lock ma nel caso generale luso della lock ` comodo e perch ci permette di scrivere codici per la gestione delle eccezioni che non tengono e conto del mutex.

5 Uso avanzato dei Mutex


Nellarticolo precedente ho parlato del mutex e del suo uso semplice con la lock guard. in questo tratter` gli altri tipi di guard e luso avanzato dei mutex o

18

unique lock o u Con unique lock un mutex pu` fare molto di pi` di quello che permette la lock guard Si pu` cercare di prenotarlo in maniera non bloccante ( se ` libero lo si occupa, o e se non ` libero non si attende) e Si pu` cercare di prenotarlo fornendo un timeout massimo di attesa ( se si o tratta di un timed mutex ovviamente) Si pu` accedere direttamente alle funzioni del mutex o e un sacco di altre cose per le quali vi rimando a una reference
3

5.1 Timed mutex e la prenotazione con timeout massimo


Disponendo di un timed mutex potremmo per esempio eseguire questo: Listing 14: Uso di un timed mutex
s t d : : timed mutex m; s t d : : u n i q u e l o c k <s t d : : timed mutex> l k (m, s t d : : chr o no : : m i l l i s e c o n d s ( 5 ) ) ; i f ( l k ) work ( ) }

Questa funzione prova ad accedere al mutex aspettando massimo 5 millisecondi, il valore della lock si usa per determinare se laccesso ` avvenuto o no e

5.2 Accesso alle funzioni interne del mutex


Attraverso la unique lock possiamo accedere alle funzioni del mutex, ma questa volta le eccezioni sono gestite. Listing 15: Accesso alle funzioni interne
s t d : : mutex m; void z o n a c r i t i c a ( ) { s t d : : u n i q u e l o c k <s t d : : mutex> l k (m) ; // i n q u e s t o momento i l mutex ` o c c u p a t o e lk . unlock ( ) ; // i n q u e s t o momento i l mutex ` l i b e r o e lk . lock ( ) ; // i n q u e s t o momento i l mutex ` d i nuovo o c c u p a t o e }
3

http://www.stdthread.co.uk/ ( lo so, non ` uciale... ma non ho trovato di meglio ) e

19

Listing 16: Costruttore non bloccante


s t d : : mutex m; void z o n a c r i t i c a ( ) { s t d : : u n i q u e l o c k <s t d : : mutex> l k (m, s t d : : d e f e r l o c k t ( ) ) ; // i n q u e s t o momento i l mutex ` l i b e r o e lk . lock ( ) ; // i n q u e s t o momento i l mutex ` o c c u p a t o e lk . unlock ( ) ; // i n q u e s t o momento i l mutex ` d i nuovo l i b e r o e }

5.2.1

Prenotazione multipla

Un problema molto comune nella programmazione concorrente ` la necessita da parte e di un processo di accedere in maniera esclusiva a pi` risorse, ` un problema che se u e gestito male pu` causare facilmente deadlock o starvation in quanto se un processo che o ha acquisito parte delle risorse necessarie attende altre risorse per continuare e queste risorse sono bloccate da altri processi anche loro in attesa tutti i processi restano bloccati in attesa luno dellaltro, in questo caso le stl ci vengono in aiuto dandoci un costrutto che permette di bloccare n mutex restando in attesa ma non bloccandoli Listing 17: Prenotazione multipla
void p r e n o t a ( s t d : : mutex a , s t d : : mutex a ) { s t d : : u n i q u e l o c k <s t d : : mutex> l o c k a ( a , s t d : : d e f e r l o c k ) ; s t d : : u n i q u e l o c k <s t d : : mutex> l o c k b ( b , s t d : : d e f e r l o c k ) ; std : : lock ( lock a , lock b ) ; // f a i q u a l c o s a d i c r i t i c o }

20

6 Il problema dei 5 loso


Un problema molto interessante che illustra la potenzialit` di questo costrutto ` il proa e blema dei 5 loso a cena, esso ` stato formulato da Dijkstra intorno al 1965, per mettere e alla prova le primitive di sincronizzazione dellepoca ( i semafori fondamentalmente) il problema ` questo (cito dal testo di Silberschatz) : e Si considerino cinque loso che passano la vita pensando e mangiando. I loso condividono un tavolo rotondo circondato da cinque sedie, una per ciascun losofo. Al centro del tavolo si trova una zuppiera colma di riso, e la tavola ` apparecchiata con cinque bacchette. Quando un losofo pensa, non e interagisce con i colleghi; quando gli viene fame, tenta di prendere le bacchette pi` vicine: quelle che si trovano tra lui e i commensali alla sua destra e u alla sua sinistra. Un losofo pu` prendere una bacchetta alla volta e non pu` o o prendere una bacchetta che si trova gi` nelle mani di un suo vicino. Quana do un losofo aamato tiene in mano due bacchette contemporaneamente, mangia senza lasciare le bacchette. Terminato il pasto, le posa e riprende a pensare. Il problema dei cinque loso ` cone siderato un classico problema di sincronizzazione, non certo per la sua importanza pratica, e neanche per antipatia verso i loso da parte degli informatici, ma perch rappresenta una vae sta classe di problemi di controllo della concorrenza, in particolare i problemi caratterizzati dalla necessit` di assea gnare varie risorse a diversi processi evitando situazioni di stallo e dattesa indenita.4 la parola starvation deriva proprio da questo problema... se non si riesce a gestire la cosa i loso muoiono di fame ( to starve in inglese)

6.1 Vari approcci ingenui


Una semplice soluzione consiste nel rappresentare ogni bacchetta con un mutex: un losofo tenta di aerrare ciascuna bacchetta eseguendo unoperazione lock su quel mutex e la posa eseguendo una unlock.
4

Parola di Silberschatz, che non me ne vogliano gli amici loso

21

Partendo da questo modello possiamo cominciare a cercare una soluzione, quella pi` u immediata potrebbe esere: un losofo prende la bacchetta a destra, poi quella a sinistra e poi mangia. ma cosa succede se tutti prendono contemporaneamente la forchetta di destra? un deadlock, muoiono di fame aspettando che qualcuno liberi qualcosa ma nessuno liberer` a mai niente. unaltra soluzione potrebbe essere: Un losofo di numero pari prende prima la forchetta di destra e poi quella di sinistra, un losofo dispari il contrario Ma questo ` barare,scartiamola e Ci rendiamo conto che un losofo deve rilasciare la bacchetta se non pu` prendere la o seconda, qualcosa del tipo Un losofo prende la bacchetta di destra, poi cerca di prendere quella di sinistra (trylock) e se non pu` le posa entrambe e riprova in seguito o ma che succede se i loso partono insieme? lalgoritmo va in loop: tutti prendono quella di destra, nessuno pu` prendere quella a sinistra, tutti posano tutto e si ricomincia, o le operazioni necessitano di essere eseguite in maniera atomica

6.2 Una soluzione funzionante


Un losofo cerca di prendere entrambe le bacchette contemporaneamente, se non pu` aspetta o Questo comportamento non pu` essere costruito direttamente usando i singoli mutex, o ma possiamo farlo con il nostro std::lock(lock a,lock b) La soluzione tradizionale necessita della costruzione di un Monitor, ma non me ne occuper` qui, sfruttiamo a pieno le stl e riduciamo il problema a una cosa banale, con o buona pace di Dijkstra5

Se siete interessati alla versione originale del problema di Dijkstra ,risolta interamente con i semafori, potete dare un occhiata a questo testo su google docs http://goo.gl/96ZaX , se poi avete anche interessi archeologici c` la scansione della versione originale dattiloscritta qui e http://www.cs.utexas.edu/~ EWD/ewd03xx/EWD310.PDF

22

Listing 18: Il problema dei 5 loso di Dijkstra


... class F i l o s o f o { public : i nt i d ; mutex d e s t r a ; mutex s i n i s t r a ; F i l o s o f o ( i nt n , mutex d , mutex s ) { i d=n ; d e s t r a=d ; s i n i s t r a=s ; } void pensa ( ) { s t r i n g a r g o me nti [ 5 ] = { A l l a fame n e l mondo , Al punto G , All o r i g i n e d e l l Universo , Al s e n s o d e l l a v i t a , A l l a domanda l a c u i r i s p o s t a e 42 }; cout << i d << : Sto pensando . . . << a r g o me nti [ i d ] << e n d l ; u s l e e p ( 10000 ( rand ( ) % 90 + 10 ) ) ; }; void mangia ( ) { p r i n t f ( %d : Cerco l e b a c c h e t t e . . . \ n , i d ) ; s t d : : u n i q u e l o c k <s t d : : mutex> l o c k a ( d e s t r a , s t d : : d e f e r l o c k ) ; s t d : : u n i q u e l o c k <s t d : : mutex> l o c k b ( s i n i s t r a , s t d : : d e f e r l o c k ) ; std : : lock ( lock a , lock b ) ; p r i n t f ( %d : Sto mangiando . . . \ n , i d ) ; u s l e e p ( 30000 ( rand ( ) % 90 + 10 ) ) ; p r i n t f ( %d : Poso l e b a c c h e t t e , s a z i o \n , i d ) ; }; void operator ( ) ( ) { p r i n t f ( %d : Penso q u i n d i sono \n , i d ) ; u s l e e p ( 30000 ( rand ( ) % 90 + 10 ) ) ; for ( i nt a =0; a <5; a++) { pensa ( ) ; mangia ( ) ; } }

}; ...

23

7 Le Condition, implementazione delle code di attesa


7.1 Le Condition
Le Condition sono un meccanismo di sincronizzazione che permette ai thread di sospendersi in attesa che avvenga un evento e di essere risvegliati quando levento accade, permettono quindi di creare delle code di attesa. Nelle STL le condition sono oggetti della classe std::condition variable. I metodi fondamentali per usarle sono tre: wait() permette di accodarsi alla condition notify one() notify all() permettono di risvegliare uno o tutti i thread in attesa sulla coda (nellimplementazione tradizionale dei pthread si chiamavano signal e broadcast) A una Condition ` sempre associato un mutex per evitare race condition che potrebbe e creare un thread che si sta preparando a una wait mentre un altro thread esegue una signal, la signal potrebbe infatti avvenire contemporaneamente alla wait e venire quindi persa creando un deadlock, pu` essere usato un mutex qualunque o si pu` creare un o o mutex proprio della condition Vediamone il funzionamento con un semplice codice: Listing 19: 10 thread si mettono in coda su una condition e un altro thread li sveglia uno per volta
#include #include #include #include #include <i o s t r e a m> <thr ea d> <c s t d l i b > <mutex> <c o n d i t i o n v a r i a b l e >

using namespace s t d ; c l a s s Worker { public : i nt numero ; s t d : : c o n d i t i o n v a r i a b l e coda ; mutex m; Worker ( i nt n , s t d : : c o n d i t i o n v a r i a b l e cond , mutex mtx ) { coda=cond ; numero=n ; m =mtx ; }

24

};

void operator ( ) ( ) { cout << Thread << numero << a l l o c a t o , mi metto i n coda << e n d l ; s t d : : u n i q u e l o c k <s t d : : mutex> l k ( m) ; coda>wa it ( l k ) ; cout << Thread << numero << r i s v e g l i a t o d a l l a coda << e n d l ; }

i nt main ( ) { s t d : : c o n d i t i o n v a r i a b l e coda= new c o n d i t i o n v a r i a b l e ( ) ; mutex m new mutex ( ) ; = i nt a ; std : : thread threads [ 1 0 ] ; for ( a =0;a <10;a++){// c r e o t h r e a d s [ a ]= new s t d : : t h r e a d ( Worker ( a +1 , coda ,m) ) ; u s l e e p ( 100000 ) ; } for ( a =0;a <10;a++){// s v e g l i o u s l e e p ( 1000 ( rand ( ) % 9 0 0 ) ) ; coda>n o t i f y o n e ( ) ; } for ( a =0;a <10;a++)// a s p e t t o t e r m i n e { t h r e a d s [ a]> j o i n ( ) ; delete ( t h r e a d s [ a ] ) ; }

};

7.2 Ma le cose non sono cos` semplici...


Malgrado di solito non succeda le speciche dei pthread non garantiscono che lordine in cui i thread si sveglino sia uguale a quello in cui si addormentano, quindi in caso di applicazioni dove lordine degli accessi ` importante questo va e vericato esplicitamente. Un altra cosa che potrebbe succedere ( e questa ` pi` grave ) ` che pi` di un thread e u e u venga liberato dalla coda, quindi i thread appena svegliati devono di nuovo vericare che la condizione che li ha fatti accodare sia stata soddisfatta ed eventualmente rimettersi in coda.

25

8 Il problema del barbiere addormentato


Vediamo un applicazione delle condition a un problema pi` complesso, il problema del u barbiere addormentato di Dijkstra. il problema ` denito in questo modo: e Un negozio di barbiere ` costituito da una sala con una sola poltrona e sulla quale si accomoda il cliente che viene servito e da una sala dattesa con n sedie. Se non ci sono clienti da servire il barbiere va a dormire. Se arriva un cliente e tutte le sedie della sala dattesa sono occupate, il cliente va via, mentre, se c` una sedia libera e il barbiere ` occupato, il cliente attender`, e e a seduto, il proprio turno. Se il barbiere sta dormendo (cio` ` libero, senza e e clienti in attesa), il cliente lo sveglia e viene servito subito. La soluzione ` pi` prolissa di quello che potrebbe sembrare a prima vista, tralaltro e u nellimplementarlo ho istintivamente usato un monitor anche se non ho accennato a questo tipo di costrutti, ne do soltanto una breve denizione OOP Un monitor ` un oggetto che incapsula la risorsa ed espone i metodi per e accedervi in maniera sicura In questo caso, come si vede nel sorgente sottostante, il contatore delle sedie occupate ` nascosto dentro la classe Stanza che mette a disposizione i metodi per occupare e e liberare una sedia mantenendoli sincronizzati attraverso un semaforo proprio della classe. Lapproccio che ho utilizzato ` questo: e

8.1 Il barbiere
1. Controlla se ci sono clienti in attesa (attraverso i metodi del Monitor) 2. se non ce ne sono si addormenta ( svegliandosi al punto 3) 3. se ce ne sono li rade e torna al punto 1 la rasatura consiste in: liberare un Cliente dalla coda della sala di attesa (attesa.notify one) , il cliente liberato occupa la coda della poltrona radere il Cliente, liberarlo dalla coda della poltrona (poltrona.notify one())

8.2 Il cliente
1. Entra nella sala, cerca di sedersi (a) Se pu` sedersi: o i. Se ` lunica persona seduta sveglia il barbiere (letto.notify one()) e ii. va al punto 2 (b) Se non pu` sedersi esce dalla sala e va al punto 5 o 26

2. Si mette in attesa nella coda della sala (attesa.wait(lk)) 3. una volta svegliato occupa il barbiere (barbiere > lock()) e si mette in attesa sulla sedia della poltrona (poltrona.wait(barbiere)). il mutex sul barbiere evita il problema di una possibile signal non voluta ( vedi sopra il paragrafo 7.2) una volta svegliato libera il barbiere ed esce dalla sala 4. va in giro a bighellonare e poi torna al punto 1 In questa implementazione sono possibili delle signal superue su un barbiere gi` a sveglio, ma sono irrilevanti

8.3 Codice completo


Listing 20: Problema del barbiere addormentato
#include #include #include #include #include <i o s t r e a m> <thr ea d> <c s t d l i b > <mutex> <c o n d i t i o n v a r i a b l e >

using namespace s t d ; i nt dormi ( f l o a t base , f l o a t v a r i a b i l e ) { i nt k =10001000; i nt b a s e i =( i nt ) ba se k ; i nt v a r i a b i l e i =0; i f ( v a r i a b i l e >0) v a r i a b i l e i=rand ( ) % ( i nt ) ( v a r i a b i l e k ) ; u s l e e p ( b a s e i+v a r i a b i l e i ) ; return b a s e i+v a r i a b i l e i ; }; c l a s s Sta nza { private : i nt o c c u p a t e ; s t d : : mutex monitor m ; public : std : : c o n d i t i o n v a r i a b l e attesa ; std std std std i nt : : mutex a t t e s a m ; : : c o n d i t i o n v a r i a b l e poltrona ; : : condition variable letto ; : : mutex b a r b i e r e ; sedie ;

27

i nt p r e n o t a ( ) { s t d : : u n i q u e l o c k <s t d : : mutex> l k ( monitor m ) ; i f ( o ccupa te>=s e d i e ) return 1; o c c u p a t e++; cout << o c c u p a t e << s e d i e o c c u p a t e <<e n d l ; return o c c u p a t e ; } i nt guarda ( ) { s t d : : u n i q u e l o c k <s t d : : mutex> l k ( monitor m ) ; return o c c u p a t e ; } i nt l i b e r a ( ) { s t d : : u n i q u e l o c k <s t d : : mutex> l k ( monitor m ) ; o ccupa te ; cout << o c c u p a t e << s e d i e o c c u p a t e <<e n d l ; return o c c u p a t e ; } Sta nza ( i nt n s e d i e ) { this >s e d i e=n s e d i e ; o c c u p a t e =0; }

};

class Barbiere { public :

i nt numero ; i nt a z i o n i ; Sta nza s t a n z a ; s t d : : u n i q u e l o c k <s t d : : mutex> b a r b i e r e ; B a r b i e r e ( Sta nza s t ) { a z i o n i =0; this >s t a n z a=s t ; b a r b i e r e= new s t d : : u n i q u e l o c k <s t d : : mutex>( sta nza >b a r b i e r e , s t d : : d e f e r l o c k t ( ) ) } void operator ( ) ( ) {

28

while ( true ) { cout << B a r b i e r e c o n t r o l l a s e c i sono c l i e n t i i n a t t e s a <<e n d l ; i f ( sta nza >guarda ()==0) { cout << B a r b i e r e s i addormenta<<e n d l ; s t d : : u n i q u e l o c k <s t d : : mutex> l k ( sta nza >b a r b i e r e ) ; stanza >l e t t o . wa it ( l k ) ; } cout << ++a z i o n i << B a r b i e r e s e r v e un c l i e n t e <<e n d l ; stanza >a t t e s a . n o t i f y o n e ( ) ; dormi ( 2 , 2 ) ; stanza >p o l t r o n a . n o t i f y o n e ( ) ;

};

class Cliente { public :

i nt numero ; Sta nza s t a n z a ; s t d : : u n i q u e l o c k <s t d : : mutex> b a r b i e r e ; C l i e n t e ( Sta nza s t , i nt n ) { s t a n z a=s t ; numero=n ; b a r b i e r e= new s t d : : u n i q u e l o c k <s t d : : mutex>( sta nza >b a r b i e r e , s t d : : d e f e r l o c k t ( ) ) } void work ( ) { cout << Thread << numero << a l l o c a t o , e n t r a d a l b a r b i e r e << e n d l ; i nt s=sta nza >p r e n o t a ( ) ; i f ( s <0) { cout << Thread << numero << t u t t e l e s e d i e sono o ccupa te , e s c e << e n d l ; return ; } else { i f ( s >0) { cout << Thread<< numero << s i s i e d e e a s p e t t a << e n d l ; i f ( s==1)

29

{ cout << Thread << numero << s v e g l i a i l b a r b i e r e << e n d l ; dormi ( 1 , 0 ) ; stanza >l e t t o . n o t i f y o n e ( ) ;

} s t d : : u n i q u e l o c k <s t d : : mutex> l k ( sta nza >a t t e s a m ) ; stanza >a t t e s a . wa it ( l k ) ; cout << Thread<< numero << s i a l z a << e n d l ; stanza >l i b e r a ( ) ;

b a r b i e r e >l o c k ( ) ; cout << Thread << numero << s i s i e t e e s i f a r a s a r e << e n d l ; stanza >p o l t r o n a . wa it ( b a r b i e r e ) ; b a r b i e r e >u n l o c k ( ) ; cout << Thread << numero << e s c e << e n d l ; } } void operator ( ) ( ) { for ( i nt a =0; a <2; a++) { work ( ) ; cout << Thread << numero << va i n g i r o a b i g h e l l o n a r e << e n d l ; dormi ( 3 0 , 3 0 ) ; } cout << Thread << numero << muore << e n d l ; }

};

i nt main ( i nt a r g c , char a r g v ) { Sta nza s t a n z a ( 5 ) ; i nt a ; std : : thread threads [ 1 5 ] ; s t d : : t h r e a d b a r b i e r e=new s t d : : t h r e a d ( B a r b i e r e (& s t a n z a ) ) ; for ( a =0; a <15; a++) // c r e o { dormi ( 0 , a ) ; t h r e a d s [ a ]= new s t d : : t h r e a d ( C l i e n t e (& sta nza , a + 1 ) ) ; } for ( a =0; a <15; a++) // a s p e t t o t e r m i n e { t h r e a d s [ a]> j o i n ( ) ; delete ( t h r e a d s [ a ] ) ;

30

};

A Ambiente di sviluppo e problemi di compilazione


Questa ` la parte pi` instabile del tutorial: come si compila il codice Cpp0X. Quando e u ho incominciato a scrivere la serie di articoli la bozza del progetto era ancora in fase di approvazione da parte dellISO, adesso che sto nendo di creare un tutorial dai miei post sul blog ` uno standard approvato ma ancora in fase di diusione. Le cose sono e cambiate e continuano a cambiare.

A.1 GNU Gcc - g++


Il compilatore che ho usato in tutti i codici, supporta il nuovo standard attraverso il ag -std=c++0x ( da g++ ovviamente) Le funzioni sono incluse nei compilatori GNU a partire dalla 4.5 A.1.1 MinGw

E possibile compilare parte dei sorgenti usando MinGw6 che ` un porting windows di e gcc, il supporto di compilazione ` completo mentre non lo ` il supporto alle librerie, in e e particolare quelle riguardanti i thread visto che i pthread non hanno un corrispettivo windows ( non open source quantomeno7 ) A.1.2 Multithreading

Il supporto ai thread della stl usa la libreria pthread per creare e gestire i thread, per attivarla ci si serve del ag -lpthread ed ` necessario che gcc includa gcc-multilib e (disponibile come pacchetto in molte distribuzioni di linux) se non funziona e in particolare se da errori del tipo fatal error: asm/errno.h bisogna applicare una patch alla libreria andate su /usr/include/ e controllate che esista la cartella asm-generic, se esiste fatene un link software su /usr/include/asm (NON rinominatela, non si sa mai che pu` o succedere con un aggiornamento di pacchetti ). Questo dovrebbe risolvere il problema, e possibilmente mentre sto scrivendo non ` pi` una cosa necessaria. e u

A.2 Microsoft Visual Studio


Fonti bene informate8 mi dicono che il supporto a Cpp0X ` completo in Visual Studio e 2010
http://www.mingw.org/ http://www.justsoftwaresolutions.co.uk/ 8 http://www.romeoxbm.tk/it/ Francesco Guastella, un mio collega in diverse cose universitarie e non
7 6

31

Riferimenti bibliograci
[1] http://www.thedarshan.com/informatica/2011/02/il-c-non-e-ancora-morto/ [2] http://www.thedarshan.com/informatica/2011/03/il-cp-e-le-lambda-c0x/ [3] http://www.thedarshan.com/informatica/2011/10/il-c-e-il-multithreading-c0x/ [4] http://www.thedarshan.com/informatica/2011/10/sincronizzazione-dei-thread-egestione-delle-risorse-con-i-mutex-c0x/ [5] http://www.thedarshan.com/informatica/2011/11/uso-avanzato-dei-mutex-i-5loso-c0x/ [6] http://www.thedarshan.com/informatica/2011/11/le-condition-implementazionedelle-code-di-attesa-il-problema-del-barbiere-addormentato-c0x/

32

Potrebbero piacerti anche