Significa che due thread non possono cambiare contemporaneamente i dati sottostanti? Oppure significa che il segmento di codice specificato verrà eseguito con risultati prevedibili quando più thread eseguono quel segmento di codice?
Significa che due thread non possono cambiare contemporaneamente i dati sottostanti? Oppure significa che il segmento di codice specificato verrà eseguito con risultati prevedibili quando più thread eseguono quel segmento di codice?
Risposte:
Da Wikipedia:
La sicurezza del thread è un concetto di programmazione informatica applicabile nel contesto di programmi multi-thread. Un pezzo di codice è sicuro per i thread se funziona correttamente durante l'esecuzione simultanea di più thread. In particolare, deve soddisfare la necessità che più thread accedano agli stessi dati condivisi e la necessità di accedere a un dato condiviso a un solo thread alla volta.
Esistono alcuni modi per ottenere la sicurezza del thread:
Re-entrancy:
Scrivere il codice in modo tale che possa essere parzialmente eseguito da un'attività, reinserito da un'altra attività e quindi ripreso dall'attività originale. Ciò richiede il salvataggio delle informazioni sullo stato nelle variabili locali per ciascuna attività, generalmente nel suo stack, anziché nelle variabili statiche o globali.
Esclusione reciproca:
L'accesso ai dati condivisi viene serializzato mediante meccanismi che assicurano che un solo thread legga o scriva i dati condivisi in qualsiasi momento. È necessaria molta attenzione se un pezzo di codice accede a più parti condivise di dati: i problemi includono condizioni di competizione, deadlock, livelock, fame e vari altri mali elencati in molti libri di testo di sistemi operativi.
Archivio locale thread:
Le variabili sono localizzate in modo che ogni thread abbia la propria copia privata. Queste variabili mantengono i loro valori oltre i limiti della subroutine e di altri codici e sono thread-safe poiché sono locali per ciascun thread, anche se il codice a cui accedono potrebbe essere rientrante.
Operazioni atomiche:
È possibile accedere ai dati condivisi utilizzando operazioni atomiche che non possono essere interrotte da altri thread. Questo di solito richiede l'uso di speciali istruzioni sul linguaggio macchina, che potrebbero essere disponibili in una libreria di runtime. Poiché le operazioni sono atomiche, i dati condivisi vengono sempre mantenuti in uno stato valido, indipendentemente da quali altri thread vi accedano. Le operazioni atomiche costituiscono la base di molti meccanismi di bloccaggio del filo.
leggi di più:
http://en.wikipedia.org/wiki/Thread_safety
in tedesco: http://de.wikipedia.org/wiki/Threadsicherheit
in francese: http://fr.wikipedia.org/wiki/Threadsafe (molto breve)
Il codice thread-safe è un codice che funzionerà anche se molti thread lo eseguono contemporaneamente.
Una domanda più istruttiva è ciò che rende il codice non thread sicuro e la risposta è che ci sono quattro condizioni che devono essere vere ... Immagina il seguente codice (ed è la traduzione automatica della lingua)
totalRequests = totalRequests + 1
MOV EAX, [totalRequests] // load memory for tot Requests into register
INC EAX // update register
MOV [totalRequests], EAX // store updated value back to memory
Mi piace la definizione dalla concorrenza Java in pratica di Brian Goetz per la sua completezza
"Una classe è sicura per i thread se si comporta correttamente quando vi si accede da più thread, indipendentemente dalla pianificazione o dall'interleaving dell'esecuzione di tali thread da parte dell'ambiente di runtime e senza alcuna sincronizzazione aggiuntiva o altro coordinamento da parte del codice chiamante. "
Come altri hanno sottolineato, la sicurezza del thread significa che un pezzo di codice funzionerà senza errori se viene utilizzato da più di un thread alla volta.
Vale la pena sapere che a volte questo ha un costo, del tempo del computer e di una codifica più complessa, quindi non è sempre desiderabile. Se una classe può essere utilizzata in modo sicuro su un solo thread, potrebbe essere meglio farlo.
Ad esempio, Java ha due classi quasi equivalenti StringBuffer
e StringBuilder
. La differenza è che StringBuffer
è thread-safe, quindi una singola istanza di a StringBuffer
può essere utilizzata da più thread contemporaneamente. StringBuilder
non è thread-safe, ed è progettato come un sostituto di prestazioni più elevate per quei casi (la stragrande maggioranza) quando String è costruito da un solo thread.
Un modo più semplice per capirlo, è ciò che rende il codice non thread-safe. Esistono due problemi principali che faranno in modo che un'applicazione filettata abbia un comportamento indesiderato.
Accesso alla variabile condivisa senza blocco
Questa variabile può essere modificata da un altro thread durante l'esecuzione della funzione. Vuoi prevenirlo con un meccanismo di blocco per essere sicuro del comportamento della tua funzione. La regola generale è quella di mantenere il blocco il più breve tempo possibile.
Deadlock causato dalla dipendenza reciproca dalla variabile condivisa
Se si hanno due variabili condivise A e B. In una funzione, si blocca prima A e poi si blocca B. In un'altra funzione, si avvia il blocco B e dopo un po 'si blocca A. Questo è un potenziale deadlock in cui la prima funzione attenderà lo sblocco di B quando la seconda funzione attenderà lo sblocco di A. Questo problema probabilmente non si verificherà nel tuo ambiente di sviluppo e solo di volta in volta. Per evitarlo, tutti i blocchi devono essere sempre nello stesso ordine.
Sì e no.
La sicurezza del thread è un po 'più che assicurarsi che i tuoi dati condivisi siano accessibili solo da un thread alla volta. Devi garantire l'accesso sequenziale ai dati condivisi, evitando allo stesso tempo condizioni di gara , deadlock , livelock e fame di risorse .
Risultati imprevedibili quando sono in esecuzione più thread non è una condizione obbligatoria del codice thread-safe, ma spesso è un sottoprodotto. Ad esempio, potresti avere uno schema produttore-consumatore impostato con una coda condivisa, un thread produttore e pochi thread consumatore e il flusso di dati potrebbe essere perfettamente prevedibile. Se inizi a presentare più consumatori vedrai risultati più casuali.
In sostanza, molte cose possono andare storte in un ambiente multi-thread (istruzioni di riordino, oggetti parzialmente costruiti, stessa variabile con valori diversi in thread diversi a causa della memorizzazione nella cache a livello di CPU ecc.).
Mi piace la definizione data da Java Concurrency in Practice :
Una [porzione di codice] è thread-safe se si comporta correttamente quando vi si accede da più thread, indipendentemente dalla pianificazione o dall'interleaving dell'esecuzione di quei thread da parte dell'ambiente di runtime e senza alcuna sincronizzazione aggiuntiva o altro coordinamento da parte del codice chiamante.
Indicano correttamente che il programma si comporta in conformità con le sue specifiche.
Esempio contrastato
Immagina di implementare un contatore. Si potrebbe dire che si comporta correttamente se:
counter.next()
non restituisce mai un valore che è già stato restituito in precedenza (non presupponiamo un overflow ecc. per semplicità)Un contatore sicuro dei thread si comporterebbe in base a tali regole indipendentemente da quanti thread vi accedono contemporaneamente (cosa che in genere non sarebbe il caso di un'implementazione ingenua).
Semplicemente: il codice funzionerà correttamente se molti thread eseguono questo codice contemporaneamente.
Non confondere la sicurezza del thread con il determinismo. Il codice thread-safe può anche essere non deterministico. Data la difficoltà di debug dei problemi con il codice thread, questo è probabilmente il caso normale. :-)
La sicurezza del thread garantisce semplicemente che quando un thread modifica o legge i dati condivisi, nessun altro thread può accedervi in modo da modificare i dati. Se il tuo codice dipende da un certo ordine per l'esecuzione per correttezza, allora hai bisogno di altri meccanismi di sincronizzazione oltre a quelli richiesti per la sicurezza del thread per garantire questo.
Vorrei aggiungere qualche informazione in più su altre buone risposte.
La sicurezza del thread implica che più thread possono scrivere / leggere dati nello stesso oggetto senza errori di incoerenza della memoria. In un programma multi-thread, un programma thread-safe non causa effetti collaterali ai dati condivisi .
Dai un'occhiata a questa domanda SE per maggiori dettagli:
Il programma thread safe garantisce la coerenza della memoria .
Dalla pagina della documentazione di Oracle sull'API simultanea avanzata:
Proprietà di coerenza della memoria:
Il capitolo 17 di Java ™ Language Specification definisce la relazione accidentale sulle operazioni di memoria come letture e scritture di variabili condivise. I risultati di una scrittura di un thread sono garantiti per essere visibili a una lettura di un altro thread solo se l'operazione di scrittura avviene prima dell'operazione di lettura .
I costrutti synchronized
e volatile
, così come i metodi Thread.start()
e Thread.join()
, possono formare relazioni che accadono prima .
I metodi di tutte le classi java.util.concurrent
e dei relativi pacchetti secondari estendono queste garanzie alla sincronizzazione di livello superiore. In particolare:
Runnable
a Executor
accadere prima dell'inizio della sua esecuzione. Allo stesso modo per Callables sottoposti a un ExecutorService
.Future
azioni precedenti prima del recupero del risultato tramite Future.get()
un altro thread.Lock.unlock, Semaphore.release, and CountDownLatch.countDown
azioni che si verificano prima di un metodo di "acquisizione" riuscito come Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
sullo stesso oggetto sincronizzatore in un altro thread.Exchanger
, si verificano azioni precedenti a exchange()
in ogni thread, prima di quelle successive allo scambio corrispondente () in un altro thread.CyclicBarrier.await
e Phaser.awaitAdvance
(oltre alle sue varianti) si verificano prima delle azioni eseguite dall'azione barriera, e le azioni eseguite dall'azione barriera si verificano prima delle azioni successive a un ritorno riuscito dall'attesa corrispondente in altri thread.Per completare altre risposte:
La sincronizzazione è solo una preoccupazione quando il codice nel tuo metodo fa una delle due cose:
Ciò significa che le variabili definite ENTRO il tuo metodo sono sempre thread-safe. Ogni chiamata a un metodo ha la sua versione di queste variabili. Se il metodo viene chiamato da un altro thread o dallo stesso thread o anche se il metodo chiama se stesso (ricorsione), i valori di queste variabili non vengono condivisi.
La pianificazione dei thread non è garantita come round robin . Un'attività può impadronirsi totalmente della CPU a spese di thread con la stessa priorità. Puoi usare Thread.yield () per avere una coscienza. Puoi usare (in java) Thread.setPriority (Thread.NORM_PRIORITY-1) per ridurre la priorità di un thread
Inoltre attenzione a:
Si e si. Implica che i dati non vengano modificati contemporaneamente da più di un thread. Tuttavia, il programma potrebbe funzionare come previsto e apparire thread-safe, anche se fondamentalmente non lo è.
Si noti che l'imprevedibilità dei risultati è una conseguenza delle "condizioni di gara" che probabilmente comportano la modifica dei dati in un ordine diverso da quello previsto.
Rispondiamo a questo esempio:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
Il countTo10
metodo aggiunge uno al contatore e quindi restituisce vero se il conteggio ha raggiunto 10. Dovrebbe restituire vero solo una volta.
Questo funzionerà fintanto che un solo thread esegue il codice. Se due thread eseguono il codice contemporaneamente possono verificarsi vari problemi.
Ad esempio, se il conteggio inizia come 9, un thread potrebbe aggiungere 1 al conteggio (creazione di 10), ma un secondo thread potrebbe immettere il metodo e aggiungere nuovamente 1 (creazione di 11) prima che il primo thread abbia la possibilità di eseguire il confronto con 10 Quindi entrambi i thread fanno il confronto e scoprono che il conteggio è 11 e nessuno dei due restituisce vero.
Quindi questo codice non è thread-safe.
In sostanza, tutti i problemi multi-threading sono causati da alcune variazioni di questo tipo di problema.
La soluzione è garantire che l'aggiunta e il confronto non possano essere separati (ad esempio circondando le due istruzioni con un qualche tipo di codice di sincronizzazione) o inventando una soluzione che non richiede due operazioni. Tale codice sarebbe sicuro per i thread.
Almeno in C ++, penso che thread-safe sia un po 'un termine improprio in quanto lascia molto fuori dal nome. Per essere thread-safe, il codice deve in genere essere proattivo al riguardo. Non è generalmente una qualità passiva.
Affinché una classe sia a prova di passo, deve avere caratteristiche "extra" che aggiungano sovraccarico. Queste funzionalità fanno parte dell'implementazione della classe e, in generale, nascoste dall'interfaccia. Cioè, thread diversi possono accedere a qualsiasi membro della classe senza mai preoccuparsi di entrare in conflitto con un accesso simultaneo da parte di un thread diverso E possono farlo in modo molto pigro, usando il semplice vecchio stile di codifica umana normale, senza doverlo fare tutta quella folle roba di sincronizzazione che è già stata introdotta nelle viscere del codice chiamato.
Ed è per questo che alcune persone preferiscono usare il termine sincronizzato internamente .
Esistono tre serie principali di terminologia per queste idee che ho incontrato. Il primo e storicamente più popolare (ma peggio) è:
Il secondo (e migliore) è:
Un terzo è:
thread safe ~ thread thread ~ sincronizzato internamente
Un esempio di un sistema sincronizzato internamente (alias thread-safe o thread proof ) è un ristorante in cui un host ti saluta alla porta e ti impedisce di fare la fila. L'host fa parte del meccanismo del ristorante per trattare con più clienti e può usare alcuni trucchi piuttosto difficili per ottimizzare i posti a sedere dei clienti in attesa, come prendere in considerazione le dimensioni della loro festa o quanto tempo sembrano avere o addirittura prendere prenotazioni per telefono. Il ristorante è sincronizzato internamente perché tutto ciò fa parte dell'interfaccia per interagire con esso.
non thread-safe (ma piacevole) ~ thread compatibile ~ sincronizzato esternamente ~ a thread libero
Supponi di andare in banca. C'è una linea, cioè una contesa per i cassieri. Poiché non sei un selvaggio, riconosci che la cosa migliore da fare nel mezzo della contesa per una risorsa è fare la fila come un essere civile. Nessuno tecnicamente ti fa fare questo. Speriamo che tu abbia la programmazione sociale necessaria per farlo da solo. In questo senso, la lobby della banca è sincronizzata esternamente. Dovremmo dire che non è sicuro? questo è ciò che l'implicazione è che se si va con il thread-safe , filo-unsafe set terminologia bipolare. Non è un ottimo insieme di termini. La migliore terminologia è sincronizzata esternamente,La lobby della banca non è ostile all'accesso di più clienti, ma non fa nemmeno il lavoro di sincronizzarli. I clienti lo fanno da soli.
Questo è anche chiamato "free threaded", dove "free" è come in "free from pidocchi" - o in questo caso, i blocchi. Bene, più precisamente, primitive di sincronizzazione. Ciò non significa che il codice possa essere eseguito su più thread senza quelle primitive. Significa solo che non viene fornito con quelli già installati e dipende da te, l'utente del codice, installarli tu stesso come ritieni opportuno. Installare le tue primitive di sincronizzazione può essere difficile e richiede una riflessione approfondita sul codice, ma può anche portare al programma più veloce possibile consentendoti di personalizzare il modo in cui il programma viene eseguito sulle attuali CPU hyperthreaded.
non thread safe (e bad) ~ thread ostile ~ non sincronizzabile
Un'analogia quotidiana di esempio di un sistema thread-ostile è un cretino con un'auto sportiva che rifiuta di usare i paraocchi e cambia corsia volenti o nolenti. Il loro stile di guida è thread ostile o insicronizzabile perché non hai modo di coordinarti con loro, e questo può portare a contese per la stessa corsia, senza risoluzione, e quindi un incidente mentre due auto tentano di occupare lo stesso spazio, senza alcun protocollo per evitarlo. Questo modello può anche essere pensato più in generale come antisociale, che preferisco perché è meno specifico per i thread e quindi più generalmente applicabile a molte aree della programmazione.
Il primo e il più vecchio set di terminologia non riesce a distinguere meglio tra ostilità e compatibilità dei thread . La compatibilità dei thread è più passiva della cosiddetta sicurezza dei thread, ma ciò non significa che il codice chiamato non sia sicuro per l'uso simultaneo dei thread. Significa solo che è passiva la sincronizzazione che lo consentirebbe, rimandandolo al codice chiamante, invece di fornirlo come parte della sua implementazione interna. Il thread compatibile è probabilmente il modo in cui il codice dovrebbe essere scritto per impostazione predefinita nella maggior parte dei casi, ma questo è purtroppo spesso considerato erroneamente come thread non sicuro, come se fosse intrinsecamente anti-sicurezza, che è un grande punto di confusione per i programmatori.
NOTA: molti manuali di software usano effettivamente il termine "thread-safe" per fare riferimento a "thread-compatibile", aggiungendo ancora più confusione a ciò che era già un disastro! Evito il termine "thread-safe" e "thread-unssafe" a tutti i costi proprio per questo motivo, poiché alcune fonti chiameranno qualcosa di "thread-safe" mentre altri lo chiameranno "thread-unssafe" perché non possono essere d'accordo se devi soddisfare alcuni standard extra per la sicurezza (primitive di sincronizzazione) o semplicemente NON essere ostile per essere considerato "sicuro". Quindi evita quei termini e usa invece i termini più intelligenti, per evitare pericolose comunicazioni errate con altri ingegneri.
In sostanza, il nostro obiettivo è quello di sovvertire il caos.
Lo facciamo creando sistemi deterministici su cui possiamo contare. Il determinismo è costoso, principalmente a causa dei costi opportunità derivanti dalla perdita di parallelismo, pipeline e riordino. Cerchiamo di ridurre al minimo la quantità di determinismo di cui abbiamo bisogno per mantenere bassi i nostri costi, evitando anche di prendere decisioni che possano erodere ulteriormente quel poco determinismo che possiamo permetterci.
La sincronizzazione dei thread riguarda l'aumento dell'ordine e la riduzione del caos. I livelli ai quali lo fai corrispondono ai termini sopra menzionati. Il livello più alto indica che un sistema si comporta sempre in modo del tutto prevedibile. Il secondo livello indica che il sistema si comporta abbastanza bene che il codice chiamante può rilevare in modo affidabile l'imprevedibilità. Ad esempio, un risveglio spurio in una variabile di condizione o un errore nel bloccare un mutex perché non è pronto. Il terzo livello indica che il sistema non si comporta abbastanza bene per giocare con chiunque altro e può ESSERE SEMPRE eseguito a thread singolo senza incorrere nel caos.
Invece di pensare al codice o alle classi come thread safe o no, penso che sia più utile pensare alle azioni come thread-safe. Due azioni sono thread-safe se si comporteranno come specificato quando eseguite da contesti di threading arbitrari. In molti casi, le classi supporteranno alcune combinazioni di azioni in modo thread-safe e altre no.
Ad esempio, molte raccolte come elenchi di array e set di hash garantiranno che se inizialmente si accede esclusivamente con un thread e non vengono mai modificate dopo che un riferimento diventa visibile ad altri thread, possono essere letti in modo arbitrario da qualsiasi combinazione di thread senza interferenze.
Ancora più interessante, alcune raccolte di set di hash come quella originale non generica in .NET, possono offrire una garanzia che fino a quando non viene mai rimosso alcun elemento, e a condizione che solo un thread li scriva, qualsiasi thread che tenta di leggere la raccolta si comporterà come se accedesse a una raccolta in cui gli aggiornamenti potrebbero essere ritardati e avvenire in ordine arbitrario, ma che altrimenti si comporteranno normalmente. Se il thread n. 1 aggiunge X e poi Y, e il thread n. 2 cerca e vede Y e quindi X, sarebbe possibile per il thread n. 2 vedere che Y esiste ma X no; se tale comportamento sia "thread-safe" dipende dal fatto che il thread n. 2 sia pronto a gestire tale possibilità.
Come nota finale, alcune classi - in particolare il blocco delle librerie di comunicazione - possono avere un metodo "close" o "Dispose" che è thread-safe rispetto a tutti gli altri metodi, ma nessun altro metodo thread-safe rispetto a l'un l'altro. Se un thread esegue una richiesta di lettura bloccante e un utente del programma fa clic su "annulla", non ci sarebbe modo per una richiesta di chiusura di essere emessa dal thread che sta tentando di eseguire la lettura. La richiesta di chiusura / eliminazione, tuttavia, può impostare in modo asincrono un flag che provocherà la cancellazione della richiesta di lettura il più presto possibile. Una volta eseguita la chiusura su qualsiasi thread, l'oggetto diventerebbe inutile e tutti i tentativi di azioni future fallirebbero immediatamente,