Cosa significa threadsafe?


124

Recentemente ho provato ad accedere a una casella di testo da un thread (diverso dal thread dell'interfaccia utente) e è stata generata un'eccezione. Diceva qualcosa sul "codice non sicuro per i thread" e così ho finito per scrivere un delegato (campione da MSDN aiutato) e chiamarlo invece.

Ma anche così non ho capito bene perché tutto il codice extra fosse necessario.

Aggiornamento: incontrerò problemi seri se controllo

Controls.CheckForIllegalCrossThread..blah =true

5
In genere, "thread safe" significa qualunque cosa la persona che usa il termine pensa che significhi, almeno per quella persona. In quanto tale, non è un costrutto di linguaggio molto utile: devi essere molto, molto più specifico quando parli del comportamento del codice in thread.


@ dave Scusa ho provato a cercare, ma ho rinunciato ... grazie comunque ..
Vivek Bernard

1
un codice che non si poneRace-Condition
Muhammad Babar

Risposte:


121

Eric Lippert ha un bel post sul blog intitolato Cos'è questa cosa che chiami "thread safe"?sulla definizione di thread safety come trovata su Wikipedia.

3 cose importanti estratte dai link:

"Un pezzo di codice è thread-safe se funziona correttamente durante l'esecuzione simultanea di più thread."

"In particolare, deve soddisfare la necessità di più thread per accedere agli stessi dati condivisi, ..."

"... e la necessità di accedere a una porzione di dati condivisa da un solo thread alla volta."

Sicuramente merita una lettura!


25
Evita solo le risposte ai link in quanto potrebbero peggiorare in qualsiasi momento in futuro.
akhil_mittal


106

Nel più semplice dei termini threadsafe significa che è sicuro accedervi da più thread. Quando si utilizzano più thread in un programma e ciascuno di essi sta tentando di accedere a una struttura o posizione di dati comune nella memoria, possono accadere diverse cose brutte. Quindi, aggiungi del codice extra per prevenire queste cose brutte. Ad esempio, se due persone stavano scrivendo lo stesso documento contemporaneamente, la seconda persona da salvare sovrascriverà il lavoro della prima persona. Per rendere il thread safe quindi, devi forzare la persona 2 ad attendere che la persona 1 completi il ​​suo compito prima di consentire alla persona 2 di modificare il documento.


11
Questa è chiamata sincronizzazione. Destra?
JavaTechnical

3
Sì. È possibile forzare i vari thread ad attendere l'accesso a una risorsa condivisa con la sincronizzazione.
Vincent Ramdhanie

Dalla risposta accettata di Gregory, sta dicendo "" Un pezzo di codice è thread-safe se funziona correttamente durante l'esecuzione simultanea da più thread. " mentre dici "Per renderlo sicuro, devi costringere la persona 1 ad aspettare"; non sta dicendo che il simultaneo è accettabile mentre stai dicendo che non lo è? Puoi spiegare per favore?
Tesoro

È la stessa cosa. Sto solo suggerendo un semplice meccanismo come esempio di ciò che rende il codice sicuro per i thread. indipendentemente dal meccanismo utilizzato, sebbene più thread che eseguono lo stesso codice non dovrebbero interferire tra loro.
Vincent Ramdhanie

Quindi questo si applica solo al codice che utilizza variabili globali e statiche? Usando il tuo esempio di persone che modificano i documenti, suppongo che non abbia senso impedire alla persona 2 di eseguire il codice di scrittura del documento su un altro documento.
Aaron Franke

18

Wikipedia ha un articolo sulla sicurezza dei thread.

Questa pagina delle definizioni (devi saltare un annuncio - scusa) lo definisce così:

Nella programmazione di computer, thread-safe descrive una porzione di programma o una routine che può essere chiamata da più thread di programmazione senza interazioni indesiderate tra i thread.

Un thread è un percorso di esecuzione di un programma. Un programma a thread singolo avrà solo un thread e quindi questo problema non si pone. Praticamente tutti i programmi GUI hanno più percorsi di esecuzione e quindi thread: ce ne sono almeno due, uno per elaborare la visualizzazione della GUI e gestire l'input dell'utente e almeno un altro per eseguire effettivamente le operazioni del programma.

Questo viene fatto in modo che l'interfaccia utente sia ancora reattiva mentre il programma sta funzionando scaricando qualsiasi processo a lunga esecuzione su qualsiasi thread non dell'interfaccia utente. Questi thread possono essere creati una volta ed esistono per tutta la durata del programma, oppure possono essere creati quando necessario e distrutti quando hanno finito.

Poiché questi thread avranno spesso bisogno di eseguire azioni comuni - i / o del disco, output dei risultati sullo schermo ecc. - queste parti del codice dovranno essere scritte in modo tale da poter gestire le chiamate da più thread, spesso a lo stesso tempo. Ciò comporterà cose come:

  • Lavorare su copie di dati
  • Aggiunta di blocchi attorno al codice critico

8

Semplicemente, thread-safe significa che un metodo o un'istanza di classe può essere utilizzata da più thread contemporaneamente senza che si verifichino problemi.

Considera il seguente metodo:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

Ora il thread A e il thread B vorrebbero entrambi eseguire AddOne (). ma A inizia per primo e legge il valore di myInt (0) in tmp. Ora per qualche motivo lo scheduler decide di fermare il thread A e rimandare l'esecuzione al thread B. Il thread B ora legge anche il valore di myInt (ancora 0) nella propria variabile tmp. Il thread B termina l'intero metodo, quindi alla fine myInt = 1. E viene restituito 1. Ora è di nuovo il turno del thread A. Il thread A continua. E aggiunge 1 a tmp (tmp era 0 per il thread A). Quindi salva questo valore in myInt. myInt è di nuovo 1.

Quindi in questo caso il metodo AddOne è stato chiamato due volte, ma poiché il metodo non è stato implementato in modo thread safe il valore di myInt non è 2, come previsto, ma 1 perché il secondo thread ha letto la variabile myInt prima che il primo thread finisse aggiornandolo.

La creazione di metodi thread-safe è molto difficile in casi non banali. E ci sono parecchie tecniche. In Java è possibile contrassegnare un metodo come sincronizzato, ciò significa che solo un thread può eseguire quel metodo in un dato momento. Gli altri thread aspettano in fila. Questo rende un metodo sicuro per i thread, ma se c'è molto lavoro da fare in un metodo, questo spreca molto spazio. Un'altra tecnica consiste nel 'contrassegnare solo una piccola parte di un metodo come sincronizzato'creando un lucchetto o un semaforo e bloccando questa piccola parte (solitamente chiamata sezione critica). Ci sono anche alcuni metodi che sono implementati come thread safe senza blocco, il che significa che sono costruiti in modo tale che più thread possano attraversarli contemporaneamente senza mai causare problemi, questo può essere il caso quando solo un metodo esegue una chiamata atomica. Le chiamate atomiche sono chiamate che non possono essere interrotte e possono essere eseguite solo da un thread alla volta.


se il metodo AddOne è stato chiamato due volte
Sujith PS

7

Nel mondo reale l'esempio per i profani è

Supponiamo che tu abbia un conto bancario con Internet e mobile banking e il tuo account abbia solo $ 10. Hai effettuato il trasferimento del saldo a un altro account utilizzando il mobile banking e, nel frattempo, hai fatto acquisti online utilizzando lo stesso conto bancario. Se questo conto bancario non è sicuro per i thread, la banca ti consente di eseguire due transazioni contemporaneamente e quindi la banca fallirà.

Threadsafe significa che lo stato di un oggetto non cambia se più thread contemporaneamente tentano di accedere all'oggetto.


5

Puoi ottenere ulteriori spiegazioni dal libro "Java Concurrency in Practice":

Una classe è thread-safe se si comporta correttamente quando si accede da più thread, indipendentemente dalla pianificazione o dall'interleaving dell'esecuzione di quei thread da parte dell'ambiente di runtime e senza ulteriore sincronizzazione o altro coordinamento da parte del codice chiamante.


4

Un modulo è thread-safe se garantisce di poter mantenere le sue invarianti di fronte all'uso multi-thread e simultaneo.

Qui, un modulo può essere una struttura dati, una classe, un oggetto, un metodo / procedura o una funzione. Fondamentalmente pezzo di codice con ambito e dati correlati.

La garanzia può essere potenzialmente limitata a determinati ambienti come una specifica architettura della CPU, ma deve valere per quegli ambienti. Se non esiste una delimitazione esplicita degli ambienti, di solito si presume che sia valido per tutti gli ambienti che il codice può essere compilato ed eseguito.

I moduli thread-unsafe possono funzionare correttamente in caso di utilizzo simultaneo e multi-thread, ma questo è spesso più dovuto a fortuna e coincidenza che a un'attenta progettazione. Anche se qualche modulo non si rompe per te, potrebbe rompersi quando viene spostato in altri ambienti.

I bug del multi-threading sono spesso difficili da eseguire il debug. Alcuni di essi si verificano solo occasionalmente, mentre altri si manifestano in modo aggressivo - anche questo può essere specifico dell'ambiente. Possono manifestarsi come risultati sottilmente sbagliati o deadlock. Possono rovinare le strutture dei dati in modi imprevedibili e causare la comparsa di altri bug apparentemente impossibili in altre parti remote del codice. Può essere molto specifico per l'applicazione, quindi è difficile fornire una descrizione generale.


3

Sicurezza del filo : un programma thread-safe protegge i suoi dati da errori di coerenza della memoria. In un programma altamente multi-thread, un programma thread-safe non causa effetti collaterali con più operazioni di lettura / scrittura da più thread sugli stessi oggetti. Diversi thread possono condividere e modificare i dati dell'oggetto senza errori di coerenza.

È possibile ottenere la sicurezza dei thread utilizzando l'API di concorrenza avanzata. Questa pagina di documentazione fornisce buoni costrutti di programmazione per ottenere la sicurezza dei thread.

Gli oggetti di blocco supportano gli idiomi di blocco che semplificano molte applicazioni simultanee.

Gli esecutori definiscono un'API di alto livello per l'avvio e la gestione dei thread. Le implementazioni degli esecutori fornite da java.util.concurrent forniscono una gestione del pool di thread adatta per applicazioni su larga scala.

Le raccolte simultanee semplificano la gestione di grandi raccolte di dati e possono ridurre notevolmente la necessità di sincronizzazione.

Le variabili atomiche hanno funzionalità che riducono al minimo la sincronizzazione e aiutano a evitare errori di coerenza della memoria.

ThreadLocalRandom (in JDK 7) fornisce una generazione efficiente di numeri pseudocasuali da più thread.

Fare riferimento ai pacchetti java.util.concurrent e java.util.concurrent.atomic anche per altri costrutti di programmazione.


1

Stai chiaramente lavorando in un ambiente WinForms. I controlli WinForms presentano affinità di thread, il che significa che il thread in cui vengono creati è l'unico thread che può essere utilizzato per accedervi e aggiornarli. Questo è il motivo per cui troverai esempi su MSDN e altrove che dimostrano come eseguire il marshalling della chiamata sul thread principale.

La normale pratica di WinForms consiste nell'avere un singolo thread dedicato a tutto il lavoro dell'interfaccia utente.


1

Trovo che il concetto di http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 sia quello che di solito considero threading non sicuro, ovvero quando un metodo ha e si basa su un effetto collaterale come una variabile globale.

Ad esempio, ho visto codice che ha formattato i numeri in virgola mobile in stringa, se due di questi vengono eseguiti in thread diversi, il valore globale di decimalSeparator può essere modificato in modo permanente in "."

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

-2

Per comprendere la sicurezza dei thread, leggi le sezioni seguenti :

4.3.1. Esempio: Tracker di veicoli che utilizza la delega

Come esempio più sostanziale di delega, costruiamo una versione del tracker del veicolo che delega a una classe thread-safe. Conserviamo le posizioni in una mappa, così si comincia con un'implementazione di Map thread-safe, ConcurrentHashMap. Memorizziamo anche la posizione utilizzando una classe Point immutabile invece di MutablePoint, mostrato nel Listato 4.6.

Listato 4.6. Classe immutabile Point utilizzata da DelegatingVehicleTracker.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Pointè thread-safe perché è immutabile. I valori immutabili possono essere condivisi e pubblicati liberamente, quindi non è più necessario copiare le posizioni quando vengono restituite.

DelegatingVehicleTrackernel Listato 4.7 non utilizza alcuna sincronizzazione esplicita; tutti gli accessi allo stato sono gestiti da ConcurrentHashMape tutte le chiavi e i valori della mappa sono immutabili.

Listato 4.7. Delega della thread safety a un ConcurrentHashMap.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

Se avessimo usato la MutablePointclasse originale invece di Point, avremmo interrotto l'incapsulamento consentendo di getLocationspubblicare un riferimento allo stato mutabile che non è thread-safe. Si noti che abbiamo leggermente modificato il comportamento della classe tracker del veicolo; mentre la versione monitor restituiva un'istantanea delle posizioni, la versione di delega restituisce una vista non modificabile ma "live" delle posizioni dei veicoli. Ciò significa che se il thread A chiama getLocationse il thread B modifica successivamente la posizione di alcuni punti, tali modifiche si riflettono nella mappa restituita al thread A.

4.3.2. Variabili di stato indipendenti

Possiamo anche delegare la thread safety a più di una variabile di stato sottostante purché le variabili di stato sottostanti siano indipendenti, il che significa che la classe composita non impone invarianti che coinvolgono più variabili di stato.

VisualComponentnel Listato 4.9 è un componente grafico che consente ai client di registrare ascoltatori per eventi di mouse e battitura. Mantiene un elenco di listener registrati di ogni tipo, in modo che quando si verifica un evento possono essere richiamati i listener appropriati. Ma non esiste alcuna relazione tra l'insieme degli ascoltatori del mouse e gli ascoltatori chiave; i due sono indipendenti e pertanto VisualComponentpossono delegare i propri obblighi di thread safety a due elenchi thread-safe sottostanti.

Listato 4.9. Delega della thread safety a più variabili di stato sottostanti.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponentusa a CopyOnWriteArrayListper memorizzare ogni elenco di ascoltatori; questa è un'implementazione di elenchi thread-safe particolarmente adatta per la gestione di elenchi di ascoltatori (vedere la Sezione 5.2.3). Ogni List è thread-safe e poiché non ci sono vincoli che accoppiano lo stato di uno allo stato dell'altro, VisualComponentpuò delegare le proprie responsabilità di thread safety agli oggetti sottostanti mouseListenerse keyListeners.

4.3.3. Quando la delega fallisce

La maggior parte delle classi composite non sono semplici come VisualComponent: hanno invarianti che mettono in relazione le variabili di stato dei componenti. NumberRangenel Listato 4.10 utilizza due AtomicIntegersper gestire il proprio stato, ma impone un vincolo aggiuntivo: il primo numero è minore o uguale al secondo.

Listato 4.10. Classe di intervallo di numeri che non protegge sufficientemente le sue invarianti. Non farlo.

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRangenon è thread-safe ; non preserva l'invariante che vincola inferiore e superiore. I metodi setLowere setUppertentano di rispettare questa invariante, ma lo fanno male. Entrambi setLowere setUppersono sequenze check-allora-atto, ma non usano bloccaggio sufficiente a renderli atomica. Se l'intervallo di numeri contiene (0, 10) e un thread chiama setLower(5)mentre un altro thread chiama setUpper(4), con un tempismo sfortunato entrambi supereranno i controlli nei setter e verranno applicate entrambe le modifiche. Il risultato è che l'intervallo ora contiene (5, 4): uno stato non valido . Quindi, mentre gli AtomicInteger sottostanti sono thread-safe, la classe composita non lo è . Perché le variabili di stato sottostanti lowereuppernon sono indipendenti,NumberRange non può semplicemente delegare la thread safety alle sue variabili di stato thread-safe.

NumberRangepotrebbe essere reso thread-safe utilizzando il blocco per mantenere le sue invarianti, come la protezione inferiore e superiore con un blocco comune. Deve anche evitare la pubblicazione in basso e in alto per impedire ai client di sovvertire le sue invarianti.

Se una classe ha azioni composte, così come NumberRange, la delega da sola non è di nuovo un approccio adatto per la sicurezza dei thread. In questi casi, la classe deve fornire il proprio blocco per garantire che le azioni composte siano atomiche, a meno che l'intera azione composta non possa essere delegata anche alle variabili di stato sottostanti.

Se una classe è composta da più variabili di stato thread-safe indipendenti e non ha operazioni con transizioni di stato non valide, può delegare la thread safety alle variabili di stato sottostanti.

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.