2012-03-08 1 views
8

Mi chiedo se il seguente codice (pseudo) è sicuro da usare. So di Flag terminato, ma ho bisogno di impostare una sorta di flag di annullamento all'operazione di ricerca ricorsiva dal thread principale e mantenere attivo il thread di lavoro. Controllerò anche la proprietà terminata, cosa manca in questo pseudo codice.È sicuro impostare il valore booleano nel thread da un altro?

type 
    TMyThread = class(TThread) 
    private 
    FCancel: Boolean; 
    procedure RecursiveSearch(const ItemID: Integer); 
    protected 
    procedure Execute; override; 
    public 
    procedure Cancel; 
end; 

procedure TMyThread.Cancel; 
begin 
    FCancel := True; 
end; 

procedure TMyThread.Execute; 
begin 
    RecursiveSearch(0); 
end; 

procedure TMyThread.RecursiveSearch(const ItemID: Integer); 
begin 
    if not FCancel then 
    RecursiveSearch(ItemID); 
end; 

procedure TMainForm.ButtonCancelClick(Sender: TObject); 
begin 
    MyThread.Cancel; 
end; 

È sicuro impostare FCancel nella proprietà booleana all'interno del thread in questo modo? Questo non si scontrerebbe con la lettura di questo flag nella procedura RecursiveSearch mentre il pulsante nel form principale (thread principale) viene premuto? O dovrò aggiungere per es. sezione critica per la lettura e la scrittura di questo valore?

Grazie mille

+1

Se ricordo correttamente, un incarico intero è atomico e come tale thread sicuro. – iamjoosy

+3

Sembra che tu abbia duplicato la proprietà 'Terminated' ... dovrebbe essere sicuro. – jpfollenius

+0

Grazie a tutti; Il commento di Smasher sulla duplicazione della proprietà terminata è la migliore spiegazione. –

risposta

16

È perfettamente sicuro farlo. Il thread di lettura leggerà sempre true o false. Non ci sarà lacrimazione perché uno Boolean è solo un singolo byte. In effetti, lo stesso vale per un valore allineato a 32 bit in un processo a 32 bit, ovvero Integer.

Questa è una gara benigna . Esiste una condizione di competizione sulla variabile booleana poiché un thread legge mentre un altro thread scrive, senza sincronizzazione. Ma la logica di questo programma non è influenzata negativamente dalla gara. In scenari più complessi, una gara del genere potrebbe essere dannosa e quindi sarebbe necessaria la sincronizzazione.

3

Sì, è sicuro, è necessario utilizzare Sezioni critiche solo quando si sta leggendo o scrivendo da/a un altro thread, all'interno dello stesso thread è sicuro.

BTW. il modo in cui si dispone di metodo RecursiveSearch definito, se (FCancel = False), allora si otterrà un overflow stack (:

+0

Abbastanza elegante :-) Ma riguardo alla sezione critica, in effetti sto scrivendo il valore dal thread principale (da cui eseguo MyThread.Cancel) nel valore che appartiene al thread worker, questo è ciò di cui mi preoccupo. Ma il miglior esempio, come ha detto Smasher, è la proprietà terminata. Grazie comunque! –

+1

Questo non è proprio corretto. Può essere perfettamente sicuro scrivere su un intero allineato da un thread e leggerlo da un thread diverso. Un buon esempio potrebbe essere un contatore di progressi che aumenta da 0 a 100. Mentre i thread di scrittura e lettura corrono sulla variabile, questa razza sarebbe benigna, almeno per questo esempio. –

+0

@David grazie per la correzione, ho sempre usato le sezioni critiche, non sapevo che poteva essere sicuro in alcune situazioni senza. – ComputerSaysNo

7

scrittura su un campo booleano da diversi thread è thread-safe - significato, l'operazione di scrittura è atomica. Nessun osservatore del campo vedrà mai un "valore parziale" mentre il valore viene scritto nel campo. Con tipi di dati più grandi, le scritture parziali sono una possibilità reale perché sono necessarie più istruzioni della CPU per scrivere il valore sul campo

Quindi, la scrittura effettiva del booleano non è un problema di sicurezza del thread.Tuttavia, come gli osservatori stanno usando quel campo booleano può essere un problema di sicurezza del thread.Nel tuo esempio, l'unico osservatore visibile è la funzione RecursiveSearch, e il suo uso di il F Annulla valore è abbastanza semplice e innocuo. L'osservatore dello stato di FCancel non modifica lo stato di FCancel, quindi questa è una dipendenza di tipo produttore/consumatore lineare/aciclico.

Se invece il codice utilizzava il campo booleano per determinare se è necessario eseguire un'operazione una tantum, le letture e le scritture semplici nel campo booleano non saranno sufficienti perché anche l'osservatore del campo booleano deve modificare campo (per indicare che l'operazione una tantum è stata eseguita). Questo è un ciclo di lettura-modifica-scrittura e questo non è sicuro quando due o più thread eseguono gli stessi passaggi al momento giusto. In tale situazione, è possibile inserire un blocco mutex attorno all'operazione singola (e al controllo e aggiornamento del campo booleano) oppure utilizzare InterlockedExchange per aggiornare e testare il campo booleano senza un mutex. È anche possibile spostare l'operazione di una volta in un costruttore di tipi statici e non è necessario mantenere alcun blocco (sebbene .NET possa utilizzare i blocchi dietro le quinte per questo).

+2

+1 ma non c'è .net qui Danny! –

+0

E il tuo commento non mi ha nostalgia per gli inizializzatori statici thread-safe integrati .......... –

+0

Il problema dei "valori parziali" dipende molto dal tipo di dati. IIRC Intel garantisce che una scrittura su un singolo byte, o su un valore a 2- o 4 byte correttamente allineato, sarà sempre una scrittura atomica. E non è difficile ottenere il compilatore per garantire quell'allineamento ... –

4

Sono d'accordo che scrivere un booleano da un thread e leggere da un altro è sicuro. Tuttavia, fai attenzione con l'incremento - questo non è atomico e potrebbe causare una condizione di gara decisamente non elevata nel tuo codice a seconda dell'implementazione.Increment/Decrement normalmente si trasforma in tre istruzioni macchina separate: carico/inc/negozio.

Questo è ciò che le chiamate API di Win32 InterlockedIncrement, InterlockedDecrement e InterlockedExchange sono per - abilitare l'incremento, il decremento ei carichi a 32 bit per verificarsi in modo atomico senza un oggetto di sincronizzazione separato.

+4

Spesso va bene se solo un thread sta scrivendo. I problemi che descrivi si applicano solo con più thread di scrittura. –