2012-02-08 6 views
34

Vorrei che il mio thread si spenga più agevolmente, quindi sto cercando di implementare un semplice meccanismo di segnalazione. Non credo che voglio un filo completamente event-driven quindi ho un lavoratore con un metodo per graceully fermarla con una sezione critica Monitor (equivalente a un C# lock credo):Va bene leggere un flag booleano condiviso senza bloccarlo quando un altro thread può impostarlo (al massimo una volta)?

DrawingThread.h

class DrawingThread { 
    bool stopRequested; 
    Runtime::Monitor CSMonitor; 
    CPInfo *pPInfo; 
    //More.. 
} 

DrawingThread.cpp

void DrawingThread::Run() { 
    if (!stopRequested) 
     //Time consuming call#1 
    if (!stopRequested) { 
     CSMonitor.Enter(); 
     pPInfo = new CPInfo(/**/); 
     //Not time consuming but pPInfo must either be null or constructed. 
     CSMonitor.Exit(); 
    } 
    if (!stopRequested) { 
     pPInfo->foobar(/**/);//Time consuming and can be signalled 
    } 
    if (!stopRequested) { 
     //One more optional but time consuming call. 
    } 
} 


void DrawingThread::RequestStop() { 
    CSMonitor.Enter(); 
    stopRequested = true; 
    if (pPInfo) pPInfo->RequestStop(); 
    CSMonitor.Exit(); 
} 

ho capito (almeno in Windows) Monitor/lock s sono i meno costosi sincronizzazione dei thread primitivo ma sono desiderosi di evitare un uso eccessivo. Dovrei essere avvolgente ogni lettura di questa bandiera booleana? Viene inizializzato su false e viene impostato una sola volta su true quando viene richiesto stop (se richiesto prima che l'attività venga completata).

I miei tutor consigliavano di proteggere anche bool perché la lettura/scrittura potrebbe non essere atomica. Penso che questa bandiera unica sia l'eccezione che conferma la regola?

+1

Consulta i consigli del tuo tutor (probabilmente hanno ragione) - anche se non lo sono, sono quelli che contrassegnano il tuo lavoro, quindi se dicono che è necessario, allora è necessario. – John3136

+6

@ John3136 Il consiglio del mio tutor ha 10 anni, questo è il mio lavoro ora. – John

risposta

38

Non è mai corretto leggere qualcosa eventualmente modificato in una thread diversa senza sincronizzazione. Quale livello di sincronizzazione è necessario dipende da ciò che stai effettivamente leggendo. Per i tipi primitivi, dovresti dare un'occhiata alle letture atomiche, ad es. nella forma di std::atomic<bool>.

Il motivo per cui la sincronizzazione è sempre necessaria è che i processori dispongano dei dati eventualmente condivisi in una riga della cache. Non ha motivo di aggiornare questo valore a un valore eventualmente modificato in un thread diverso se non esiste alcuna sincronizzazione. Peggio ancora, tuttavia, se non c'è sincronizzazione, potrebbe scrivere il valore sbagliato se qualcosa memorizzato vicino al valore è cambiato e sincronizzato.

+0

Non penso che la mia piattaforma includa 'std :: atomic'. – John

+1

'std :: atomic ' fa parte di C++ 2011. Generalmente viene implementato in cima alle strutture fornite dalla piattaforma che vengono fornite con qualsiasi cosa venga scelta nella piattaforma di rispetto. Effettivamente consiste nell'utilizzare un'operazione di processore appropriata. Il modo in cui questi vengono chiamati e utilizzati dipende dalla piattaforma. 'std :: atomic ' fornisce un'interfaccia piacevole e portatile a loro. –

+0

Sarebbe molto più semplice per me proteggere semplicemente l'accesso con 'CSMonitor' come stavo cercando di evitare. Quindi non posso trascurarlo in modo sicuro qui, anche con 'volatile bool stopRequested' perché quel valore potrebbe essere memorizzato nella cache della CPU e ignorante degli aggiornamenti anche attraverso la funzione di accesso monitorata,' RequestStop', a meno che non venga letto all'interno di una sezione critica? – John

10

L'assegnazione booleana è atomica. Non è questo il problema

Il problema è che un thread potrebbe non vedere le modifiche apportate a una variabile da un thread diverso a causa del riordino o del caching delle istruzioni del compilatore o della CPU (ovvero il thread che legge il flag booleano può leggere un valore memorizzato nella cache, del valore attuale aggiornato).

La soluzione è una fence di memoria, che in realtà viene implicitamente aggiunta dalle istruzioni di blocco, ma per una singola variabile è eccessivo. Basta dichiararlo volatile.

+1

"L'assegnazione booleana è atomica": solo per interesse, è richiesto dallo standard C o C++? –

+16

Nota che dichiarare qualcosa 'volatile' in C++ ha ** nessun ** effetto sulla sincronizzazione ** del tutto **! L'uso di 'volatile' è per il compilatore per impedirgli di omettere e/o riordinare le istruzioni (questo è diverso per esempio dalla semantica di' volatile' in Java). Devi anche dire alla CPU che è necessaria la sincronizzazione. È necessario utilizzare la direttiva corrispondente, ad es. 'std :: atomic '. –

+2

@NiklasB. Penso che ciò che si intende è: non si può finire con uno stato incoerente di un 'bool' visto da un thread. È davvero atomico in questo senso. Tuttavia, questo non significa che il valore sia sincronizzato in alcun modo con diversi core. Affinché questi siano atomici è necessario la sincronizzazione ad es. usando 'std :: atomic '. –

1

No, devi proteggere ogni accesso, poiché i compilatori e le cpus moderni riordinano il codice senza le tue attività di multithreading in mente. L'accesso in lettura da diversi thread potrebbe funzionare, ma non deve funzionare.

4

La risposta, credo, è "dipende". Se stai usando C++ 03, il threading non è definito nello standard, e dovrai leggere cosa dicono il tuo compilatore e la tua libreria di thread, anche se questo genere di cose viene solitamente chiamato "razza benigna" and is usually OK.

Se si utilizza C++ 11, le razze benigne sono comportamenti indefiniti. Anche quando un comportamento non definito non ha senso per il tipo di dati sottostante. Il problema è che i compilatori possono supporre che i programmi non abbiano un comportamento indefinito, and make optimizations based on that (vedere anche la Parte 1 e la Parte 2 collegati da lì). Ad esempio, il compilatore potrebbe decidere di leggere il flag una volta e memorizzare il valore nella cache perché è un comportamento indefinito scrivere sulla variabile in un altro thread senza alcun tipo di mutex o barriera di memoria.

Ovviamente, è possibile che il tuo compilatore prometta di non eseguire tale ottimizzazione. Avrai bisogno di guardare.

La soluzione più semplice è utilizzare std::atomic<bool> in C++ 11 o qualcosa di simile a Hans Boehm's atomic_ops altrove.

+1

Non "dipende": se è necessario condividere dati mutabili su più thread, anche se è solo un 'bool', è necessario un qualche tipo di sincronizzazione. In sostanza si tratta di informare la CPU per disporre gli oggetti attualmente memorizzati nella cache per renderli visibili. –

+1

Dipende nel senso che (1) anche quando lo Standard dice che qualcosa non è definito, le implementazioni ** sono ** permesse per definire cosa accadrà e le razze benigne sono abbastanza comuni che c'è una buona possibilità che il compilatore possa farlo; e (2) mentre le razze benigne sono definitivamente indefinite in C++ 11, è più difficile dire che per C++ 03 (anche se il primo articolo che ho linkato dice esattamente questo, ovviamente mostra anche quanto sia diffuso questo tipo di corse di dati siamo). –

Problemi correlati