|
Il passo successivo nell'ambito della concorrenza vede la gestione di accessi multipli, ovvero da parte di più threads, di una risorsa condivisa. Evidentemente si tratta di un processo delicato in quanto è necessario
Mutex si avvolge intorno ai dati che intende proteggere e in questo modo garantisce la loro protezione. Vediamo un po' di codice, anche questo tratto dalla documentazione ufficiale e che riprendo in quanto molto semplice e chiaro:
quindi, alla riga 1 importiamo il crate necessario e alla 3 avvogliamo il Mutex (che è in realtà uno smart pointer) intorno alla variabile di nome "data". La riga 5 è importante in quanto attraverso di essa e in particolare tramite lock() si ottiene il blocco della risorsa per un suo utilizzo esclusivo. Tramite unwrap() estraiamo invece un MutexGuard (oppure ne riceviamo un panic se ci sono problemi). Ma che cos'è un MutexGuard? Si tratta di una cosidetta guardia RAII (Resource Acquisition Is Initialization) che mantiene il controllo della risorsa finchè necessario e la rilascia quando esce dallo scope. Si tratta di realtà di una struct definita internamente come segue: pub struct MutexGuard<'a, T: ?Sized + 'a> { /* private fields */ } ovvero: 'a (Lifetime parameter): Assicura che l'istanza di MutexGuard non possa vivere più a lungo del mutex da cui è stato creato. Questo impedisce l'uso di un riferimento al contenuto del mutex dopo che il lock è stato rilasciato. T: ?Sized + 'a: Indica che T è un tipo generico che può essere di dimensioni dinamiche (?Sized permette di gestire i tipi che non hanno una dimensione nota a compile-time), e che deve vivere almeno quanto 'a. MutexGuard implementa Deref, DerefMut e Drop che permette il rilascio della risorsa e anche Send e Sync (se T lo è) e proprio grazie a DerefMut possiamo, alla riga 6, modificare il valore di locked_data. Bene, ora che ne abbiamo spoegato il funzionamento di Mutex, almeno per sommi capi, è il momento di addentrarci un po' di più nell'argomento. Il passo successivo è la condivisione di una risorsa tra più thread. Il seguente programma condivide una stringa attraverso 3 thread i quali andranno ad aggiungere, a tale stringa, una lettera ciascuno. Il codice risolve il seguente problema: prende una variabile di tipo string e ogni thread, che supponiamo siano 3, deve per 5 volte aggiungere rispettivamente; thread1 la lettera 'a', thread2 la lettera 'b', thread3 la lettera 'c'.
In questo programma usiamo il nostro Mutex per l'accesso sicuro e in suo aiuto anche Arc che, come visto, permette la condivisione sicura. Ovviamente, alla riga 2 ci serve thread per la creazione dei thread. La riga 6 è un po' il cuore di tutto. Creiamo una stringa vuota, avvolta da Mutex per l'accesso concorrente a sua volta avvolto da Arc per la condivisione ovvero la creazione di più copie sicure. La riga 8 prepara il solito vettore dei thread per attenderne la fine gestita alle righe 20 e 21. Nulla di nuovo direi. Alla riga 9 iteriamo su un vettore di caratteri via referenza. Interessante è la riga 10 nella quale effettuiamo la clonazione (necessaria: Mutex non supporta Copy) di Arc aumentando il numero di riferimenti. In questo modo tutti i thread puntano al Mutex in maniera indipendente. Alla 11 troviamo move necessario per portare il thread dentro al loop, come abbiamo già visto. La riga 13 è invece il motore del programma, lock cerca di prendere possesso dell'elemento, unwrap() che forza l'esito del lock(se la risorsa è già occupata il programma crasha). Alla 14 aggiungiamo il carattere al vec. Il resto è deja vu. Il seguente schema mostra il funzionamento grafico del programma. ![]() Una avvertenza che è posta in chiaro sia sulla documentazione ufficiale che su vari testi è che Mutex non garantisce una totale protezione rispetto ai deadlock ovvero quelle situazioni di stallo che si verificano quando per esempio due thread attendono l'uno la fine dell'altro. Vedremo più avanti un esempio, tenete presente che nessun linguaggio può, per sua natura, evitare del tutto questo problema. |