Multithreading: Come usare le classi di sincronizzazione MFC
La sincronizzazione dell'accesso alle risorse tra thread è un problema comune durante la scrittura di applicazioni multithreading. L'accesso simultaneo di due o più thread agli stessi dati può causare risultati indesiderati e imprevedibili. Ad esempio, un thread potrebbe aggiornare il contenuto di una struttura mentre un altro thread legge il contenuto della stessa struttura. È sconosciuto quali dati riceveranno il thread di lettura: i dati precedenti, i dati appena scritti o possibilmente una combinazione di entrambi. MFC fornisce una serie di classi di accesso di sincronizzazione e sincronizzazione per facilitare la risoluzione di questo problema. Questo argomento illustra le classi disponibili e come usarle per creare classi thread-safe in una tipica applicazione multithreading.
Un'applicazione multithreading tipica ha una classe che rappresenta una risorsa da condividere tra thread. Una classe completamente thread-safe progettata correttamente non richiede di chiamare alcuna funzione di sincronizzazione. Tutto viene gestito internamente alla classe, consentendo di concentrarsi su come usare al meglio la classe, non sul modo in cui potrebbe essere danneggiato. Una tecnica efficace per la creazione di una classe completamente thread-safe consiste nell'unire la classe di sincronizzazione nella classe di risorse. L'unione delle classi di sincronizzazione nella classe condivisa è un processo semplice.
Ad esempio, prendere un'applicazione che gestisce un elenco collegato di account. Questa applicazione consente di esaminare fino a tre account in finestre separate, ma solo uno può essere aggiornato in qualsiasi momento specifico. Quando un account viene aggiornato, i dati aggiornati vengono inviati in rete a un archivio dati.
Questa applicazione di esempio usa tutti e tre i tipi di classi di sincronizzazione. Poiché consente di esaminare contemporaneamente fino a tre account, usa CSemaphore per limitare l'accesso a tre oggetti di visualizzazione. Quando si verifica un tentativo di visualizzare un quarto account, l'applicazione attende fino a quando una delle prime tre finestre si chiude o non riesce. Quando un account viene aggiornato, l'applicazione usa CCriticalSection per assicurarsi che venga aggiornato un solo account alla volta. Al termine dell'aggiornamento, segnala AEvent che rilascia un thread in attesa della segnalazione dell'evento. Questo thread invia i nuovi dati all'archivio dati.
Progettazione di una classe thread-safe
Per rendere una classe completamente thread-safe, aggiungere prima di tutto la classe di sincronizzazione appropriata alle classi condivise come membro dati. Nell'esempio di gestione dell'account precedente, un CSemaphore
membro dati verrebbe aggiunto alla classe di visualizzazione, un CCriticalSection
membro dati verrebbe aggiunto alla classe elenco collegato e un CEvent
membro dati verrebbe aggiunto alla classe di archiviazione dei dati.
Aggiungere quindi chiamate di sincronizzazione a tutte le funzioni membro che modificano i dati nella classe o accedono a una risorsa controllata. In ogni funzione è necessario creare un oggetto CSingleLock o CMultiLock e chiamare la funzione dell'oggetto Lock
. Quando l'oggetto lock esce dall'ambito e viene eliminato definitivamente, il distruttore dell'oggetto chiama Unlock
l'utente, rilasciando la risorsa. Naturalmente, puoi chiamare Unlock
direttamente se vuoi.
La progettazione della classe thread-safe in questo modo consente di usarla in un'applicazione multithreading altrettanto facilmente come una classe non thread-safe, ma con un livello di sicurezza superiore. L'incapsulamento dell'oggetto di sincronizzazione e dell'oggetto di accesso alla sincronizzazione nella classe della risorsa offre tutti i vantaggi della programmazione completamente thread-safe senza lo svantaggio di mantenere il codice di sincronizzazione.
Nell'esempio di codice seguente viene illustrato questo metodo usando un membro dati ( m_CritSection
di tipo CCriticalSection
), dichiarato nella classe di risorse condivise e un CSingleLock
oggetto . La sincronizzazione della risorsa condivisa (derivata da CWinThread
) viene tentata creando un CSingleLock
oggetto usando l'indirizzo dell'oggetto m_CritSection
. Viene effettuato un tentativo di bloccare la risorsa e, quando ottenuto, il lavoro viene eseguito sull'oggetto condiviso. Al termine del lavoro, la risorsa viene sbloccata con una chiamata a Unlock
.
CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...
singleLock.Unlock();
Nota
CCriticalSection
, a differenza di altre classi di sincronizzazione MFC, non dispone dell'opzione di una richiesta di blocco a tempo. Il periodo di attesa per cui un thread diventa libero è infinito.
Gli svantaggi di questo approccio sono che la classe sarà leggermente più lenta della stessa classe senza aggiungere gli oggetti di sincronizzazione. Inoltre, se è possibile che più thread eliminino l'oggetto, l'approccio unito potrebbe non sempre funzionare. In questo caso, è preferibile mantenere oggetti di sincronizzazione separati.
Per informazioni sulla determinazione della classe di sincronizzazione da usare in situazioni diverse, vedere Multithreading: Quando usare le classi di sincronizzazione. Per altre informazioni sulla sincronizzazione, vedere Sincronizzazione in Windows SDK. Per altre informazioni sul supporto del multithreading in MFC, vedere Multithreading con C++ e MFC.