8

Per favore perdoni il mio titolo un po 'umoristico. Io uso due definizioni diverse della parola "sicuro" in essa (ovviamente).È sicuro utilizzare le funzioni thread "non sicure"?

Sono piuttosto nuovo al threading (beh, ho usato il threading per molti anni, ma solo forme molto semplici di esso). Ora mi trovo di fronte alla sfida di scrivere implementazioni parallele di alcuni algoritmi e i thread devono lavorare sugli stessi dati. Si consideri il seguente errore newbie:

const 
    N = 2; 

var 
    value: integer = 0;  

function ThreadFunc(Parameter: Pointer): integer; 
var 
    i: Integer; 
begin 
    for i := 1 to 10000000 do 
    inc(value); 
    result := 0; 
end; 

procedure TForm1.FormCreate(Sender: TObject); 
var 
    threads: array[0..N - 1] of THandle; 
    i: Integer; 
    dummy: cardinal; 
begin 

    for i := 0 to N - 1 do 
    threads[i] := BeginThread(nil, 0, @ThreadFunc, nil, 0, dummy); 

    if WaitForMultipleObjects(N, @threads[0], true, INFINITE) = WAIT_FAILED then 
    RaiseLastOSError; 

    ShowMessage(IntToStr(value)); 

end; 

Un principiante potrebbe aspettare il codice qui sopra per visualizzare il messaggio 20000000. Infatti, il primo value è uguale a 0 e quindi lo inc è 20000000 volte. Tuttavia, poiché la procedura inc non è "atomica", i due thread saranno in conflitto (suppongo che lo inc faccia tre cose: legge, incrementa e salva), quindi molti dei numeri inc verranno effettivamente persi '. Un valore tipico che ottengo dal codice sopra riportato è 10030423.

La soluzione più semplice è utilizzare InterlockedIncrement anziché Inc (che sarà molto più lento in questo esempio sciocco, ma non è questo il punto). Un'altra soluzione consiste nel posizionare inc all'interno di una sezione critica (sì, anche in questo esempio sciocco sarà molto lento).

Ora, nella maggior parte degli algoritmi reali, i conflitti non sono così comuni. In realtà, potrebbero essere molto rari. Uno dei miei algoritmi crea DLA fractals e una delle variabili che ho ogni ora e poi il numero di particelle adsorbite è I inc. I conflitti qui sono molto rari e, cosa più importante, non mi interessa davvero se la variabile riassume 20000000, 20000008, 20000319 o 19999496. Quindi, è allettante non utilizzare InterlockedIncrement o sezioni critiche, poiché essi solo gonfia il codice e lo rende (marginalmente) più lento a no (per quanto posso vedere) beneficio.

Tuttavia, la mia domanda è: possono esserci più gravi conseguenze dei conflitti rispetto a un valore leggermente "errato" della variabile incrementale? Il programma può bloccarsi, per esempio?

Certo, questa domanda potrebbe sembrare sciocco, perché, dopo tutto, il costo di utilizzo InterlockedIncrement invece di inc è piuttosto bassa (in molti casi, ma non tutti!), E quindi è (forse) stupido non per giocare al sicuro. Ma penso anche che sarebbe bello sapere come funziona davvero a livello teorico, quindi continuo a pensare che questa domanda sia molto interessante.

+0

Immagino che il downvote sia stato causato dal titolo umoristico. –

risposta

10

Il programma non si arresta mai a causa di una corsa sull'incremento di un numero intero che viene utilizzato solo come conteggio. Tutto ciò che può andare storto è che non si ottiene la risposta corretta. Ovviamente se si stesse usando il numero intero come indice in un array, o forse si trattasse di un puntatore, si potrebbero avere problemi.

A meno che non si stia incrementando frequentemente questo valore, è difficile immaginare che un incremento interbloccato sia abbastanza costoso da farvi notare la differenza di prestazioni.

Inoltre, l'approccio più efficiente consiste nel far sì che ogni thread mantenga il proprio conteggio privato. Quindi sommare tutti i singoli conteggi dei thread quando si uniscono i thread alla fine del calcolo. In questo modo ottieni il meglio da entrambi i mondi. Nessuna contesa sull'incremento e la risposta corretta. Naturalmente, è necessario adottare misure per assicurarsi di non essere scoperti da false sharing.

+0

Grazie per la risposta. Riguardo l'ultima parte, so che questo è un esempio stupido, ma volevo solo ottenere una conferma della mia ipotesi che la cosa peggiore che può accadere in un caso come questo è il risultato ovvio (o "leggermente spento") . –

+0

Il rischio è ovviamente anche basato su quello che fai con quel conteggio. Se è solo per la visualizzazione, non è un problema, ma potrebbe essere un problema se lo usi per iterare o altre cose in cui un conteggio sbagliato potrebbe significare perdite di memoria o accesso alla memoria che è stato deallocato –

+0

@Marco: Ovviamente. Avevo solo paura che il "conflitto" potesse essere potenzialmente pericoloso di per sé. –

Problemi correlati