Domanda

In precedenza ho scritto alcuni molto semplice codice multithread, e sono sempre stato consapevole del fatto che in qualsiasi momento ci potrebbe essere un contesto di passare proprio nel bel mezzo di quello che sto facendo, così ho sempre custoditi accesso alla variabili condivise attraverso una classe CCriticalSection che entra nella sezione critica sulla costruzione e lascia la distruzione. So che questo è abbastanza aggressivo e mi entrare e uscire sezioni critiche spesso e talvolta egregiamente (ad esempio all'inizio di una funzione quando ho potuto mettere il CCriticalSection all'interno di un blocco di codice più stretto), ma il mio codice non va in crash e si corre abbastanza veloce .

Al lavoro il mio codice multithread deve essere un più stretto, solo bloccaggio / sincronizzazione al livello più basso necessario.

Al lavoro stavo cercando di eseguire il debug del codice multithreading, e mi sono imbattuto in questo:

EnterCriticalSection(&m_Crit4);
m_bSomeVariable = true;
LeaveCriticalSection(&m_Crit4);

Ora, m_bSomeVariable è un Win32 BOOL (non volatile), che per quanto ne so è definito come un int, e sulla lettura x86 e scrivere questi valori è una singola istruzione, e da cambi di contesto si verificano su un confine di istruzioni allora non c'è bisogno di sincronizzare questa operazione con una sezione critica.

Ho fatto un po 'di ricerca on-line per vedere se questa operazione non ha bisogno di sincronizzazione, e mi si avvicinò con due scenari è fatto:

  1. La CPU implementa esecuzione fuori ordine o il secondo filo è in esecuzione su un nucleo diverso e il valore aggiornato non è scritto nella RAM per l'altro core vedere; e
  2. L'int non è di 4 byte allineati.

Credo che il numero 1 può essere risolto utilizzando la parola chiave "volatile". In VS2005 e poi il compilatore C ++ circonda l'accesso a questa variabile con barriere di memoria, in modo che la variabile è sempre completamente scritto / letto alla memoria di sistema principale prima di utilizzarlo.

Numero 2 non posso verificare, non so il motivo per cui l'allineamento di byte sarebbe fare la differenza. Non so il set di istruzioni x86, ma ha bisogno di mov di essere dato un indirizzo allineato a 4 byte? Se non lo fanno è necessario utilizzare una combinazione di istruzioni? Che sarebbe introdurre il problema.

...

DOMANDA 1: Fa usando la parola chiave "volatile" (implicitamente con barriere di memoria e alludendo al compilatore di non ottimizzare questo codice) assolvere un programmatore dalla necessità di sincronizzare un 4 byte / 8 -byte su x86 / x64 variabile tra le operazioni di lettura / scrittura?

DOMANDA 2:? C'è l'esplicita esigenza che la variabile sia di 4 byte / 8 byte allineato

Ho fatto un po 'di scavare nel nostro codice e le variabili definite nella classe:

class CExample
{

private:

    CRITICAL_SECTION m_Crit1; // Protects variable a
    CRITICAL_SECTION m_Crit2; // Protects variable b
    CRITICAL_SECTION m_Crit3; // Protects variable c
    CRITICAL_SECTION m_Crit4; // Protects variable d

    // ...

};

Ora, a me questo sembra eccessivo. Ho pensato che le sezioni critiche discussioni sincronizzati tra un processo, quindi se hai uno si può entrare e nessun altro thread in questo processo in grado di eseguire. Non v'è alcuna necessità di una sezione critica per ogni variabile che si desidera proteggere, se siete in una sezione critica allora nient'altro può interrompere voi.

Credo che l'unica cosa che può cambiare le variabili al di fuori di una sezione critica è se il processo condivide una pagina di memoria con un altro processo (si può farlo?) E l'altro processo inizia a cambiare i valori. Mutex aiuterebbero anche qui, i mutex denominati vengono condivisi tra i processi, o solo i processi con lo stesso nome?

Domanda 3: è la mia analisi di sezioni critiche corretta, e deve questo codice essere riscritto per usare i mutex? Ho avuto uno sguardo in altri oggetti di sincronizzazione (semafori e spinlocks), stanno più adatto qui?

Domanda 4: Dove sono critici sezioni / mutex / semafori / spinlocks più adatto? Cioè, che problema di sincronizzazione dovrebbero essere applicati a. C'è una grande penalizzazione delle prestazioni per la scelta di uno sopra l'altro?

E mentre siamo su di esso, ho letto che spinlocks non devono essere utilizzati in un ambiente multithreading single-core, a mul multi-coreambiente tithreaded. Così, Domanda 5: È questo torto, o se no, perché è giusto

Grazie in anticipo per eventuali risposte:)

È stato utile?

Soluzione

Q1: utilizzando la parola chiave "volatile"

  

In VS2005 e successivamente il compilatore C ++ circonda accesso a questa variabile utilizzando barriere memoria, assicurando che la variabile è sempre completamente scritto / lettura alla memoria di sistema prima di utilizzarlo.

Esattamente. Se non si sta creando codice portabile, Visual Studio implementa esattamente in questo modo. Se si vuole essere portatile, le opzioni sono attualmente "limitata". Fino a C ++ 0x non c'è modo portatile come specificare operazioni atomiche con garanzia di lettura ordinazione / scrittura ed è necessario implementare soluzioni per-piattaforma. Detto questo, spinta già fatto il lavoro sporco per voi, ed è possibile utilizzare sua atomica primitive .

Q2: bisogni variabile da 4 byte / 8 byte allineato

?

Se non tenerli allineati, sei al sicuro. Se non lo fai, le regole sono complicate (linee di cache, ...), quindi il modo più sicuro è quello di tenerli allineati, in quanto questo è facile da raggiungere.

Q3:? Se questo codice di essere riscritto per utilizzare i mutex

sezione critica è un mutex leggero. A meno che non è necessario sincronizzare tra i processi, utilizzare le sezioni critiche.

Q4: Dove sono fondamentali sezioni / mutex / semafori / spinlocks più adatto

critiche anche possibile < a href = "http://msdn.microsoft.com/en-us/library/ms683476%28v=VS.85%29.aspx" rel = "nofollow noreferrer"> do YD LQ DFFHOHUD attese per voi.

Q5: spinlocks non devono essere utilizzati in un singolo core

Blocco Spin utilizza il fatto che, mentre la CPU attesa è in rotazione, un'altra CPU può rilasciare il blocco. Questo non può accadere con una sola CPU, quindi è solo uno spreco di tempo. Su multi-CPU spin lock possono essere una buona idea, ma dipende da quanto spesso lo spin attesa avrà successo. L'idea è in attesa per un breve periodo è molto più veloce poi fare contesto passare lì e poi di nuovo, quindi, se l'attesa è probabile che sia breve, è meglio aspettare.

Altri suggerimenti

1) Non volatile, dice solo ricaricare il valore dalla memoria ogni volta che è ancora possibile per essere la metà aggiornato.

Modifica: 2) Windows fornisce alcune funzioni atomiche. Cercare il funzioni "bloccato" .

I commenti hanno portato a me fare un po 'di più la lettura su. Se andate a leggere attraverso il Guida di programmazione Intel Sistema Si può vedere che ci allineati leggere e scrive sono atomiche.

8.1.1 Operazioni atomiche garantite Il processore Intel486 (e più recenti processori da) garantisce che il seguente operazioni di memoria di base saranno sempre eseguite atomicamente:
• La lettura o la scrittura di un byte
• Leggere o scrivere una parola allineati su un 16-bit di confine
• lettura o scrittura di una doppia parola allineata su un 32-bit di confine
Il processore Pentium (e più recenti processori da) garantisce che il seguente operazioni di memoria supplementari saranno sempre effettuate atomicamente:
• lettura o scrittura di un quadword allineata su un 64-bit di confine
• 16 bit accede a locazioni di memoria non memorizzati nella cache che si adattano all'interno di un 32 bit bus dati
I processori della famiglia P6 (e più recenti processori dal) garantiscono che il seguente funzionamento della memoria aggiuntiva sarà sempre effettuata atomicamente:
• Unaligned 16, 32, e 64-bit accede alla memoria cache che si adattano all'interno di una cache linea
Accessi alla memoria cacheable che vengono suddiviso su larghezze di autobus, linee di cache, e limiti di pagina non sono garantiti per essere atomica da parte del processore Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, famiglia P6, Pentium, e processori Intel486. Il processore Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium M, Pentium 4, Intel Xeon, e P6 processori della famiglia forniscono segnali di controllo del bus che consentire sottosistemi di memoria esterni per rendere scissione accessi atomico; però, accede ai dati non allineati saranno seriamente compromettere le prestazioni del processore e dovrebbe essere evitato. Un'istruzione x87 o un istruzioni SSE che accede ai dati più grande di un quadword può essere implementato utilizzando memoria accessi multipli. Se un tale negozi di istruzione alla memoria, alcuni degli accessi possono completare (scrivendo a memoria), mentre un altro causa l'operazione di difetto per ragioni architettoniche (ad esempio a causa di una voce page-table che è contrassegnato “non presente”). In questo caso, gli effetti degli accessi completati possono essere visibili al software anche se l'istruzione generale provocato un errore. Se TLB invalidazione è stata ritardata (vedi Sezione 4.10.3.4), possono accadere Anche gli errori di pagina anche se tutti gli accessi sono alla stessa pagina.

Quindi, in pratica sì, se si fa un lettura / scrittura a 8 bit da qualsiasi indirizzo a 16-bit di lettura / scrittura da un indirizzo allineato a 16 bit etc etc si sono sempre operazioni atomiche. E 'anche interessante notare che si può fare di lettura della memoria non allineati / scrive all'interno di una cacheline su una macchina moderna. Le regole sembrano piuttosto complessa anche se così non vorrei contare su di loro, se fossi in te. Evviva la commentatori thats che una buona esperienza di apprendimento per me che uno:)

3) Una sezione critica tenterà di girare serratura per la serratura diverse volte e poi blocca un mutex. Spin blocco può succhiare la potenza della CPU non fare nulla e un mutex può prendere un po 'per fare il suo roba. CriticalSections sono una buona scelta se non è possibile utilizzare le funzioni intrecciate.

4) Ci sono sanzioni di performance per la scelta di uno piuttosto che un altro. La sua una bella grande chiedere di passare attraverso i benefici di tutto qui. L'aiuto di MSDN ha un sacco di buone informazioni su ciascuno di questi. Ho sugegst loro lettura.

5) È possibile utilizzare un blocco di selezione in un unico ambiente filettato sua generalmente non è necessario se, come gestione filo significa che non è possibile avere 2 processori accedono agli stessi dati contemporaneamente. Semplicemente non è possibile.

1: volatile in sé è praticamente inutile per multithreading. Garantisce che verrà eseguita la lettura / scrittura, piuttosto che memorizzare il valore in un registro, e garantisce che la lettura / scrittura, non verrà riordinato rispetto ad altri volatile legge / scrive . Ma si può ancora essere riordinata rispetto a quelli non volatili, che è fondamentalmente il 99,9% del codice. Microsoft ha ridefinito volatile per avvolgere anche tutti gli accessi a barriere di memoria, ma che non è garantito essere il caso in generale. Sarà solo silenzio break su qualsiasi compilatore che definisce volatile come standard fa. (Il codice verrà compilato ed eseguito, semplicemente non sarà thread-safe più)

A parte questo, legge / scrive a intero dimensioni degli oggetti sono atomiche su x86 fino a quando l'oggetto è ben allineata. (Non avete alcuna garanzia di quando la scrittura avverrà però. Il compilatore e la CPU può riordinare esso, quindi è atomico, ma non thread-safe)

2:. Sì, l'oggetto deve essere allineato per la lettura / scrittura di essere atomica

3: Non proprio. Solo un thread può eseguire codice all'interno di una data sezione critica alla volta. Altri thread possono ancora eseguire altro codice. Così si può avere quattro variabili ogni protetti da una sezione critica diversa. Se hanno tutti condiviso la stessa sezione critica, sarei in grado di manipolare oggetto 1, mentre si sta manipolando oggetto 2, che è inefficiente e vincola il parallelismo più del necessario. Se essi sono protetti da diverse sezioni critiche, abbiamo appena non possiamo sia manipolare il stesso oggetto contemporaneamente.

4: spinlocks sono raramente una buona idea. Sono utili se si aspetta un filo di dover solo aspettare un tempo molto breve prima di essere in grado di acquisire il blocco, e è assolutamente neeed latenza minima. Evita la commutazione di contesto operativo che è un'operazione relativamente lenta. Al contrario, il filo si trova proprio di un ciclo continuo di polling di una variabile. Quindi maggiore utilizzo della CPU (il nucleo non viene liberata in modo da eseguire un altro thread in attesa del spinlock), ma il filo sarà in grado di continuare a non appena come il blocco viene rilasciato.

Per quanto riguarda gli altri, le caratteristiche prestazionali sono più o meno lo stesso: basta usare a seconda di quale ha la semantica più adatto per le vostre esigenze. Tipicamente sezioni critiche sono più conveniente per proteggere variabili condivise, e mutex può essere facilmente utilizzato per impostare un "flag" permettendo ad altri thread di procedere.

Come per non usare spinlocks in un ambiente single-core, ricorda che lo spinlock realtà non cede. Un filo attesa su uno spinlock non è effettivamente messa in attesa permettendo al sistema operativo per pianificare filo B per eseguire. Ma dal momento che A è in attesa su questo spinlock, qualche altro thread è costretta a rilasciare quella serratura. Se si dispone di un solo nucleo, allora detto altro filo sarà in grado di funzionare quando A si è disattivato. Con un sistema operativo sana, che sta per accadere prima o poi comunque come parte del cambio di contesto normale. Ma poiché sappiamo che A non sarà in grado di ottenere il blocco fino a quando B ha avuto un tempo di esecuzione e stampa la serratura, si starebbe meglio se A solo ceduto subito, è stato messo in una coda di attesa da parte del sistema operativo, e riavviato quando B ha rilasciato il blocco. Ed è quello che tutti i altri tipi di blocco fanno. Uno spinlock sarà ancora lavoro in un unico ambiente di base (assumendo un sistema operativo con multitasking preemptive), sarà solo essere molto molto inefficiente.

Non utilizzare volatile. Ha praticamente nulla a che fare con il filo di sicurezza. Vedere qui per il low-down.

L'assegnazione di BOOL non ha bisogno di primitive di sincronizzazione. Funzionerà bene senza alcuno sforzo particolare da parte vostra.

Se si desidera impostare la variabile e quindi fare in modo che un altro thread vede il nuovo valore, è necessario stabilire un qualche tipo di comunicazione tra i due thread. Basta bloccare immediatamente prima di assegnare Raggiunge nulla perché l'altro thread potrebbe essere venuto e andato prima che è stata acquisita la serratura.

Un'ultima parola di cautela: filettatura è estremamente difficile da ottenere. I programmatori più esperti tendono ad essere meno agevole l'utilizzo fili, che dovrebbe impostare campanelli d'allarme per chi è inesperto con il loro uso. Vi consiglio caldamente di utilizzare alcune primitive di alto livello per implementare la concorrenza nella vostra app. Passando strutture dati immutabili tramite code sincronizzato è un approccio che riduce notevolmente il pericolo.

volatile non implica barriere di memoria.

Questo significa soltanto che farà parte dello stato percepito del modello di memoria. L'implicazione di questo è che il compilatore non può ottimizzare la variabile di distanza, e non può eseguire operazioni sulla variabile solo in registri della CPU (sarà effettivamente caricare e memorizzare in memoria).

Per quanto non ci sono barriere di memoria implicita, il compilatore può riordinare le istruzioni a volontà. L'unica garanzia è che l'ordine in cui diverse variabili volatili sono di lettura / scrittura sarà lo stesso come nel codice:

void test() 
{
    volatile int a;
    volatile int b;
    int c;

    c = 1;
    a = 5;
    b = 3;
}

Con il codice sopra (supponendo che c non è ottimizzato via) l'aggiornamento c può avvenire prima o dopo le aggiornamenti a e b, fornendo 3 possibili esiti. Gli aggiornamenti a e b sono garantiti per essere eseguite in ordine. c può essere ottimizzato via facilmente da qualsiasi compilatore. Con informazioni sufficienti, il compilatore può anche ottimizzare la via a e b (se può essere dimostrato che nessun altro thread leggono le variabili e che non sono legati a un array di hardware (quindi in questo caso, possono infatti essere rimossi). Si noti che la norma non richiede un comportamento specifico, ma piuttosto uno stato percepibile con la regola as-if.

Domande 3: CRITICAL_SECTIONs e lavoro mutex, più o meno, allo stesso modo. Un mutex Win32 è un oggetto del kernel, in modo che possa essere condiviso tra i processi, e attese avanti con WaitForMultipleObjects, che non si può fare con un CRITICAL_SECTION. D'altra parte, un CRITICAL_SECTION è più leggera e quindi più veloce. Ma la logica del codice dovrebbe essere influenzato da cui si utilizza.

È anche commentato che "non v'è alcuna necessità di una sezione critica per ogni variabile che si desidera proteggere, se siete in una sezione critica allora nient'altro può interromperla." Questo è vero, ma il compromesso è che accede a una qualsiasi delle variabili avrebbe bisogno di tenere quella serratura. Se le variabili possono essere aggiornati in modo indipendente per significato, si stanno perdendo l'occasione per Parallelizzazione tali operazioni. (Dato che questi sono i membri dello stesso oggetto, però, vorrei riflettere prima di concludere che essi possano realmente accedere in modo indipendente l'uno dall'altro.)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top