Cosa significa "si verifica fortemente prima"?


9

La frase "si verifica fortemente prima" viene utilizzata più volte nella bozza standard C ++.

Ad esempio: Termination [basic.start.term] / 5

Se il completamento dell'inizializzazione di un oggetto con durata dell'archiviazione statica si verifica fortemente prima di una chiamata a std :: atexit (consultare, [support.start.term]), la chiamata alla funzione passata a std :: atexit viene sequenziato prima della chiamata al distruttore per l'oggetto. Se una chiamata a std :: atexit si verifica fortemente prima del completamento dell'inizializzazione di un oggetto con durata dell'archiviazione statica, la chiamata al distruttore per l'oggetto viene messa in sequenza prima che la chiamata alla funzione venga passata a std :: atexit . Se una chiamata a std :: atexit si verifica fortemente prima di un'altra chiamata a std :: atexit, la chiamata alla funzione passata alla seconda chiamata std :: atexit viene messa in sequenza prima che la chiamata alla funzione passi alla funzione prima chiamata std :: atexit.

E definito in Data race [intro.races] / 12

Una valutazione A avviene fortemente prima di una valutazione D se

(12.1) A è sequenziato prima di D, o

(12.2) A si sincronizza con D, e sia A che D sono operazioni atomiche sequenzialmente coerenti ([atomics.order]), oppure

(12.3) ci sono valutazioni B e C tali che A viene sequenziato prima che B, B avvenga semplicemente prima di C e C sia sequenziato prima di D, oppure

(12.4) esiste una valutazione B tale che A si verifica fortemente prima di B e B si verifica fortemente prima di D.

[Nota: informalmente, se A si verifica fortemente prima di B, allora A sembra essere valutato prima di B in tutti i contesti. Succede fortemente prima di escludere le operazioni di consumo. - nota finale]

Perché è stato introdotto "fortemente accade prima"? Intuitivamente, qual è la sua differenza e relazione con "succede prima"?

Cosa significa "A sembra essere valutato prima di B in tutti i contesti" nella nota?

(Nota: la motivazione di questa domanda sono i commenti di Peter Cordes sotto questa risposta .)

Progetto di preventivo standard aggiuntivo (grazie a Peter Cordes)

Ordine e coerenza [atomics.order] / 4

Esiste un unico ordine totale S su tutte le operazioni memory_order :: seq_cst, inclusi i recinti, che soddisfa i seguenti vincoli. In primo luogo, se A e B sono operazioni memory_order :: seq_cst e A accade fortemente prima di B, allora A precede B in S. Secondo, per ogni coppia di operazioni atomiche A e B su un oggetto M, dove A è ordinato per coerenza prima di B, S deve soddisfare le seguenti quattro condizioni:

(4.1) se A e B sono entrambe operazioni memory_order :: seq_cst, allora A precede B in S; e

(4.2) se A è un'operazione memory_order :: seq_cst e B accade prima di un memory_order :: seq_cst fence Y, allora A precede Y in S; e

(4.3) se un memory_order :: seq_cst fence si verifica prima che A e B sia un'operazione memory_order :: seq_cst, allora X precede B in S; e

(4.4) se un memory_order :: seq_cst fence si verifica prima che A e B accadano prima di un memory_order :: seq_cst fence Y, allora X precede Y in S.


1
L'attuale bozza di norma fa riferimento anche a "A si verifica fortemente prima di B" come condizione per una regola da applicare seq_cst, in Atomics 31.4 Ordine e coerenza: 4 . Questo non è nello standard C ++ 17 n4659 , in cui 32.4 - 3 definisce l'esistenza di un singolo ordine totale di operazioni seq_cst coerenti con l'ordine "succede prima" e gli ordini di modifica per tutte le località interessate ; il "fortemente" è stato aggiunto in una bozza successiva.
Peter Cordes,

2
@PeterCordes Penso che il commento escludendo il consumo, affermando che è HB "in tutti i contesti" / "forte" e parlare di chiamate a puntatori di funzioni è una sorta di omaggio morto. Se un programma multithread chiama atexit()in un thread e exit()in un altro, non è sufficiente che gli inizializzatori portino solo una dipendenza basata sul consumo solo perché i risultati differiscono quindi se sono exit()stati invocati dallo stesso thread. Una mia risposta più vecchia riguardava questa differenza.
Iwillnotexist Idonotexist,


@IwillnotexistIdonotexist Puoi anche uscire da un programma MT? Non è fondamentalmente un'idea rotta?
curioso

1
@curiousguy Questo è lo scopo di exit(). Qualsiasi thread può uccidere l'intero programma uscendo, oppure il thread principale può uscire da return-ing. Il risultato è la chiamata dei atexit()gestori e la morte di tutti i thread qualunque cosa stessero facendo.
Iwillnotexist Idonotexist,

Risposte:


5

Perché è stato introdotto "fortemente accade prima"? Intuitivamente, qual è la sua differenza e relazione con "succede prima"?

Preparati anche per "succede semplicemente prima"! Dai un'occhiata a questa istantanea attuale di cppref https://en.cppreference.com/w/cpp/atomic/memory_order

inserisci qui la descrizione dell'immagine

Sembra che "accada semplicemente prima" venga aggiunto in C ++ 20.

Succede semplicemente prima

Indipendentemente dai thread, la valutazione A avviene semplicemente prima della valutazione B se si verifica una delle seguenti condizioni:

1) A è sequenziato prima di B

2) A si sincronizza con B

3) A accade semplicemente prima di X e X accade semplicemente prima di B

Nota: senza consumare operazioni, le relazioni si verificano semplicemente prima e prima.

Quindi Simply-HB e HB sono uguali, tranne per il modo in cui gestiscono le operazioni di consumo. Vedi HB

Succede-prima

Indipendentemente dai thread, la valutazione A avviene prima della valutazione B se si verifica una delle seguenti condizioni:

1) A è sequenziato prima di B

2) Un inter-thread avviene prima di B

L'implementazione è necessaria per garantire che la relazione che precede sia aciclica, introducendo un'ulteriore sincronizzazione se necessario (può essere necessario solo se è coinvolta un'operazione di consumo, vedi Batty et al)

In che modo differiscono per quanto riguarda il consumo? Vedi Inter-Thread-HB

Inter-thread succede prima

Tra thread, valutazione A inter-thread si verifica prima della valutazione B se si verifica una delle seguenti condizioni

1) A sincronizza con B

2) A è ordinato per dipendenza prima di B

3) ...

...

Un'operazione ordinata per dipendenza (ovvero utilizza il rilascio / consumo) è HB ma non necessariamente Simply-HB.

Consumare è più rilassato che acquisito, quindi se ho capito bene, HB è più rilassato di Simply-HB.

Succede fortemente prima

Indipendentemente dai thread, la valutazione A si verifica fortemente prima della valutazione B se si verifica una delle seguenti condizioni:

1) A è sequenziato prima di B

2) A si sincronizza con B e sia A che B sono operazioni atomiche sequenzialmente coerenti

3) A è sequenziato prima di X, X accade semplicemente prima di Y e Y è sequenziato prima di B

4) A accade fortemente prima di X e X accade fortemente prima di B

Nota: informalmente, se A accade fortemente prima di B, allora A sembra essere valutato prima di B in tutti i contesti.

Nota: si verifica fortemente prima che vengano escluse le operazioni di consumo.

Quindi un'operazione di rilascio / consumo non può essere Strongly-HB.

Il rilascio / acquisizione può essere HB e Simply-HB (perché il rilascio / acquisizione si sincronizza con) ma non è necessariamente Strongly-HB. Perché Strongly-HB afferma specificamente che A deve sincronizzarsi con B E deve essere un'operazione sequenzialmente coerente.

                            Is happens-before guaranteed?

                        HB             Simply-HB          Strongly-HB

relaxed                 no                 no                 no
release/consume        yes                 no                 no      
release/acquire        yes                yes                 no
S.C.                   yes                yes                yes

Cosa significa "A sembra essere valutato prima di B in tutti i contesti" nella nota?

Tutti i contesti: tutti i thread / tutte le CPU vedono (o "alla fine saranno d'accordo") lo stesso ordine. Questa è la garanzia di coerenza sequenziale: un ordine di modifica totale globale di tutte le variabili. Le catene di acquisizione / rilascio garantiscono solo l'ordine di modifica percepito per i thread che partecipano alla catena. I thread al di fuori della catena sono teoricamente autorizzati a vedere un ordine diverso.

Non so perché sono stati introdotti Strongly-HB e Simply-HB. Forse per aiutare a chiarire come operare intorno al consumo? Strongly-HB ha delle belle proprietà: se un thread osserva A accadendo fortemente prima che B, sappia che tutti i thread osserveranno la stessa cosa.

La storia del consumo:

Paul E. McKenney è responsabile del consumo secondo gli standard C e C ++. Consume garantisce l'ordinamento tra l'assegnazione del puntatore e la memoria a cui punta. È stato inventato a causa del DEC Alpha. Il DEC Alpha poteva dereferenziare speculativamente un puntatore, quindi aveva anche un recinto di memoria per impedirlo. DEC Alpha non è più realizzato e nessun processore oggi ha questo comportamento. Consumare è pensato per essere molto rilassato.


1
Santo cielo. Mi pento quasi di aver fatto quella domanda. Voglio tornare ad affrontare i semplici problemi di C ++ come le regole di invalidazione dell'iteratore, la ricerca del nome dipendente dall'argomento, gli operatori di conversione definiti dall'utente del modello, la deduzione dell'argomento del modello, quando la ricerca del nome appare in una classe base nel membro di un modello e quando si può convertire in una base virtuale all'inizio della costruzione di un oggetto.
curioso

Ri: consumare. Sostieni che il destino dell'ordinamento dei consumi è legato al destino di DEC Alpha e non ha alcun valore al di fuori di quel particolare arco?
curioso

1
Questa è una bella domanda. Analizzandoci di più ora, sembra che il consumo possa teoricamente migliorare le prestazioni di archi debolmente ordinati come ARM e PowerPC. Dammi un po 'più di tempo per esaminarlo.
Humphrey Winnebago,

1
Direi che il consumo esiste a causa di tutti gli ISA debolmente ordinati diversi da Alpha. In Alpha asm le uniche opzioni sono rilassate e acquisiscono (e seq-cst), non l'ordinamento di dipendenza. mo_consumeintende sfruttare l'ordinamento della dipendenza dei dati su CPU reali e formalizzare che il compilatore non può interrompere la dipendenza dei dati tramite la previsione del ramo. ad es. int *p = load(); tmp = *p;potrebbe essere rotto dal compilatore introducendo if(p==known_address) tmp = *known_address; else tmp=*p;se avesse qualche motivo per aspettarsi che un certo valore di puntatore fosse comune. È legale per rilassarsi ma non consumare.
Peter Cordes,

@PeterCordes right ... gli archi con un ordinamento debole devono emettere una barriera di memoria per acquisire, ma (teoricamente) non per consumare. Sembra che tu pensi che se l'Alfa non fosse mai esistita avremmo ancora consumato? Inoltre, stai sostanzialmente dicendo che consumare è una barriera di compilazione elaborata (o "standard").
Humphrey Winnebago il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.