2011-01-13 6 views
6

In caso ho qualche codice che sembra qualcosa di simile:seguito puntatori in un ambiente di multithreading

typedef struct { 
    bool some_flag; 

    pthread_cond_t c; 
    pthread_mutex_t m; 
} foo_t; 

// I assume the mutex has already been locked, and will be unlocked 
// some time after this function returns. For clarity. Definitely not 
// out of laziness ;) 
void check_flag(foo_t* f) { 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
} 

C'è qualcosa nello standard C impedendo un ottimizzatore di riscrittura check_flag come:

void check_flag(foo_t* f) { 
    bool cache = f->flag; 
    while(cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 

In altre parole, il codice generato ha per seguire il puntatore f ogni volta attraverso il ciclo, oppure il compilatore è libero di estrarre la dereferenziazione?

Se lo è libero di estrarlo, esiste un modo per impedirlo? Devo aggiungere una parola chiave volatile da qualche parte? Non può essere il parametro di check_flag perché ho in programma di avere altre variabili in questa struttura che non mi interessa il compilatore che ottimizza in questo modo.

potrei ricorrere a:

void check_flag(foo_t* f) { 
    volatile bool* cache = &f->some_flag; 
    while(*cache) 
     pthread_cond_wait(&f->c, &f->m); 
} 
+0

+1 per pensare a questo tipo di problema prima di scrivere codice threadato per tentativi ed errori! –

risposta

3

Normalmente, si dovrebbe cercare di bloccare il mutex pthread prima di attendere sulla condizione oggetto come il rilascio pthread_cond_wait chiamata il mutex (e riacquistare prima di tornare). Quindi, la tua funzione check_flag dovrebbe essere riscritta in questo modo per conformarsi alla semantica sulla condizione pthread.

void check_flag(foo_t* f) { 
    pthread_mutex_lock(&f->m); 
    while(f->flag) 
     pthread_cond_wait(&f->c, &f->m); 
    pthread_mutex_unlock(&f->m); 
} 

Per quanto riguarda la questione se il compilatore è consentito di ottimizzare la lettura del campo flag, questo answer spiega in modo più dettagliato di quanto posso.

Fondamentalmente, il compilatore conosce la semantica di pthread_cond_wait, pthread_mutex_lock e pthread_mutex_unlock. Sa che non può ottimizzare la lettura della memoria in quelle situazioni (la chiamata a pthread_cond_wait in questo esempio). Non c'è nessuna nozione di barriera di memoria qui, solo una conoscenza speciale di certe funzioni, e qualche regola da seguire in loro presenza.

C'è un'altra cosa che ti protegge dall'ottimizzazione eseguita dal processore. Il tuo processore medio è in grado di riordinare l'accesso alla memoria (lettura/scrittura) a condizione che la semantica sia conservata, e lo fa sempre (poiché consente di aumentare le prestazioni). Tuttavia, questa interruzione quando più di un processore può accedere allo stesso indirizzo di memoria. Una barriera di memoria è solo un'istruzione al processore che gli dice che può spostare la lettura/scrittura che sono state emesse prima della barriera ed eseguirle dopo la barriera. Li ha finiti ora.

+0

Ciò significa che il compilatore non può memorizzare nella cache il valore di 'p-> some_flag' in un registro? Non sono sicuro delle implicazioni di una barriera di memoria. La mente li spiega un po '? –

+0

Modificata la risposta. È più chiaro ora? –

+0

Sì, grazie. –

3

Come scritto, il compilatore è libero di memorizzare nella cache il risultato come descritto o anche in un modo più sottile - inserendolo in un registro. È possibile impedire che questa ottimizzazione si verifichi rendendo la variabile volatile. Ma questo non è necessariamente sufficiente - non dovresti codificarlo in questo modo! È necessario utilizzare le variabili di condizione come prescritto (blocco, attesa, sblocco).

Cercare di aggirare la libreria è brutto, ma peggiora. Forse leggendo il testo di Hans Boehm sul tema generale da PLDI 2005 ("I thread non possono essere implementati come una libreria"), o molti dei suoi follow-on articles (che portano a lavorare su un modello di memoria C++ revisionato) metteranno il timore di Dio in te e sterzare di nuovo verso il dritto e stretto :).

+0

Non riesco a leggere quel foglio senza pagare soldi.Sono troppo povero per imparare =/ –

+1

Link alternativo alternativo al rapporto tecnico: http://www.hpl.hp.com/techreports/2004/HPL-2004-209.html – EmeryBerger

+0

Grazie per il collegamento. Adoro leggere i documenti di Hans. Sono troppo divertenti però. Ho intenzione di perdere un'ora della mia vita. –

7

Nel caso generale, anche se il multi-threading non è stato coinvolto e il ciclo sembrava:

void check_flag(foo_t* f) { 
    while(f->flag) 
     foo(&f->c, &f->m); 
} 

il compilatore sarebbe in grado di mettere in cache il test f->flag. Questo perché il compilatore non può sapere se una funzione (come foo() sopra) possa modificare qualunque oggetto f stia puntando.

In casi particolari (foo() è visibile al compilatore, e tutti i puntatori passati al check_flag() sono noti per non essere alias o comunque modificabile foo()) il compilatore potrebbe essere in grado di ottimizzare il controllo.

Tuttavia, pthread_cond_wait() deve essere implementato in modo da impedire tale ottimizzazione.

Vedi Does guarding a variable with a pthread mutex guarantee it's also not cached?:

Potreste anche essere interessati a risposta di Steve Jessop a: Can a C/C++ compiler legally cache a variable in a register across a pthread library call?

Ma fino a che punto si vuole prendere le questioni sollevate dalla carta di Boehm nel proprio lavoro dipende da voi. Per quanto ne so, se vuoi prendere la posizione che il pthreads non può/non può dare la garanzia, allora stai essenzialmente prendendo in considerazione che il pthreads è inutile (o almeno non fornisce garanzie di sicurezza, che Penso che per riduzione abbia lo stesso risultato). Anche se questo potrebbe essere vero nel senso più stretto (come indicato nel documento), probabilmente non è una risposta utile. Non sono sicuro di quale opzione avresti a parte pthreads su piattaforme basate su Unix.

+0

Vorrei poter accettare due risposte, ma non posso. Ho appena scelto quello con il rappresentante più basso. Erano ugualmente utili però! –

+1

Questa è la migliore risposta, ma mi piace la logica dell'OP per accettare l'altro. :-) Per quello che vale, le funzioni di sincronizzazione pthread sono specificate come barriere di memoria complete. Il modo in cui viene implementato non è affare dell'azienda; è garantito che funzioni. –

+0

"_pthread_cond_wait() deve essere implementato in modo da impedire tale ottimizzazione." "Sono curioso di sapere come' pthread_cond_wait' possa essere ragionevolmente implementato in un modo che permetta l'ottimizzazione (errata)! – curiousguy

1

Volatile è per questo scopo. Affidarsi al compilatore per conoscere le pratiche di codifica in pthread mi sembra un po 'folle, sebbene; i compilatori sono piuttosto intelligenti in questi giorni. In effetti, il compilatore probabilmente vede che stai eseguendo il ciclo per testare una variabile e non la memorizzerà in un registro per quel motivo, non perché ti vede usare i pthreads. Usa volatile se ti interessa davvero.

Tipo di piccola nota divertente. Abbiamo una #fine VOLATILE che è "volatile" (quando pensiamo che il bug non possa essere il nostro codice ...) o vuoto. Quando pensiamo di avere un crash a causa dell'ottimizzatore che ci uccide, lo definiamo "volatile" che mette volatile di fronte a quasi tutto. Quindi testiamo per vedere se il problema scompare. Finora ... i bug sono stati lo sviluppatore e non il compilatore! chi avrebbe pensato !? Abbiamo sviluppato una libreria di threading "non bloccante" e "non bloccante" ad alte prestazioni. Abbiamo una piattaforma di test che la martella al punto di migliaia di gare al secondo. Quindi, non abbiamo mai rilevato un problema che richiede volatilità! Finora gcc non ha mai memorizzato nella cache una variabile condivisa in un registro. yah ... anche noi siamo sorpresi. Stiamo ancora aspettando la nostra possibilità di usare volatile!

+0

Mi piace la storia =) avere un upvote. –

+0

"I compositori sono piuttosto intelligenti in questi giorni." Quando il compilatore ha pensato che una chiamata a una funzione non avesse alcun effetto su nulla? – curiousguy