2009-05-16 2 views
5

Ho una domanda abbastanza specifica sulla programmazione concorrente in C. Ho fatto un bel po 'di ricerche su questo ma ho visto diverse risposte contrastanti, quindi spero in qualche chiarimento. Ho un programma che è qualcosa di simile alla seguente (scusate per il blocco di codice lunghetto):accesso variabile simultaneo in c

typedef struct { 
    pthread_mutex_t mutex; 
    /* some shared data */ 
    int eventCounter; 
} SharedData; 

SharedData globalSharedData; 

typedef struct { 
    /* details unimportant */ 
} NewData; 

void newData(NewData data) { 
    int localCopyOfCounter; 

    if (/* information contained in new data triggers an 
     event */) { 
    pthread_mutex_lock(&globalSharedData.mutex); 
    localCopyOfCounter = ++globalSharedData.eventCounter; 
    pthread_mutex_unlock(&globalSharedData.mutex); 
    } 
    else { 
    return; 
    } 

    /* Perform long running computation. */ 

    if (localCopyOfCounter != globalSharedData.eventCounter) { 
    /* A new event has happened, old information is stale and 
     the current computation can be aborted. */ 
    return; 
    } 

    /* Perform another long running computation whose results 
    depend on the previous one. */ 

    if (localCopyOfCounter != globalSharedData.eventCounter) { 
    /* Another check for new event that causes information 
     to be stale. */ 
    return; 
    } 

    /* Final stage of computation whose results depend on two 
    previous stages. */ 
} 

C'è un pool di thread a servizio del collegamento per i dati in entrata, in modo più istanze di newData possono essere in esecuzione allo stesso tempo . In un ambiente multiprocessore ci sono due problemi di cui sono a conoscenza nell'ottenere il contatore che gestisce parte di questo codice in modo corretto: impedire al compilatore di memorizzare nella cache la copia contatore condivisa in un registro in modo che altri thread non possano vederlo e forzare il CPU per scrivere l'archivio del valore del contatore in memoria in modo tempestivo in modo che altri thread possano vederlo. Preferirei non utilizzare una chiamata di sincronizzazione attorno ai contatori perché una lettura parziale del valore del contatore è accettabile (produrrà un valore diverso dalla copia locale, che dovrebbe essere sufficiente per concludere che si è verificato un evento). Sarebbe sufficiente dichiarare volatile il campo eventCounter in SharedData o devo fare qualcos'altro qui? C'è anche un modo migliore per gestire questo?

risposta

2

Sfortunatamente, lo standard C dice molto poco sulla concorrenza. Tuttavia, la maggior parte dei compilatori (gcc e msvc, comunque) considereranno una lettura volatile come se avesse acquire semantics - la variabile volatile verrà ricaricata dalla memoria ad ogni accesso. Ciò è desiderabile, il tuo codice com'è ora potrebbe finire per confrontare i valori memorizzati nella cache nei registri. Non sarei nemmeno sorpreso se i due confronti fossero ottimizzati.

Quindi la risposta è sì, rendere il eventCounter volatile. In alternativa, se non si desidera limitare troppo il compilatore, è possibile utilizzare la seguente funzione per eseguire le letture di eventCounter.

int load_acquire(volatile int * counter) { return *counter; } 

if (localCopy != load_acquire(&sharedCopy)) 
    // ... 
0

impedire al compilatore di cache copia contatore locale in un registro così altri thread non può vedere che

La copia contatore locale è "locale", creato sull'esecuzione stack e visibile solo al thread in esecuzione. Ogni altro thread viene eseguito in uno stack diverso e presenta la propria variabile contatore locale (nessuna concorrenza).

Il contatore globale deve essere dichiarato volatile per evitare l'ottimizzazione del registro.

+0

Si è assolutamente corretto sulla copia locale, ho appena corretto quell'errore nel mio testo. – user98166

0

È inoltre possibile utilizzare un assembly o un compilatore codificato a mano intrinsics che garantisce controlli atomici contro il mutex, ma anche atomicamente ++ e - il proprio contatore.

volatile è useless questi giorni, per la maggior parte, si dovrebbe guardare alle barriere di memoria che sono altre facilità della CPU di basso livello per aiutare con contesa multi-core.

Tuttavia, il miglior consiglio che posso dare, sarebbe per voi di approfondire le varie librerie di supporto multi-core gestite e native. Immagino che alcuni dei più vecchi come OpenMP o MPI (basati sui messaggi), stiano ancora scalciando e le persone continueranno a parlare di quanto siano cool ... comunque per la maggior parte degli sviluppatori, qualcosa come le nuove API di Intel TBB o Microsoft's, ho anche appena scavato up this code project article, apparentemente sta usando cmpxchg8b che è la via hardware di basso livello che ho citato inizialmente ...

Buona fortuna.