2011-06-18 1 views
5

Ho letto molti esempi sui thread di chiusura .. ma perché dovresti bloccarli? Dalla mia comprensione, quando si avviano i thread senza unirli, essi competeranno con il thread principale e tutti gli altri thread per le risorse e quindi verranno eseguiti, a volte simultaneamente, a volte no.Perché dovresti bloccare i thread?

Il blocco garantisce che i thread NON vengano eseguiti contemporaneamente?

Inoltre, che problemi con i thread in esecuzione simultanea? Non è ancora meglio? (esecuzione generale più veloce)

Quando blocchi i fili, li blocca tutti o puoi scegliere quali vuoi bloccare? (Qualunque sia la chiusura in realtà lo fa ...)

mi riferisco a utilizzare le funzioni di blocco come blocco() e acquisire nel modulo threading btw ...

+0

No, non sto chiedendo del GIL, conosco i limiti di esso in Python e sono felice con esso, la domanda riguarda il locking dei thread con acquire() e release() non relativi al GIL (diverso da esso ha bloccato il nome) – MistahX

+1

È stato ricodificato, in quanto non esclusivo per 'python'. –

+0

retagurato come python, mi riferisco ai metodi di blocco nel modulo di threading, ho dimenticato di aggiungere che in – MistahX

risposta

11

Un blocco consente di forzare più thread per l'accesso una risorsa alla volta, piuttosto che tutti cercano di accedere alla risorsa contemporaneamente.

Come si nota, in genere si desidera che i thread vengano eseguiti contemporaneamente. Tuttavia, immagina di avere due thread e che stanno entrambi scrivendo sullo stesso file. Se provano a scrivere sullo stesso file nello stesso momento, il loro output si mescolerà e nessuno dei thread riuscirà effettivamente a inserire nel file ciò che desidera.

Ora forse questo problema non si presenterà sempre. Il più delle volte, i thread non cercheranno di scrivere sul file tutto in una volta. Ma a volte, forse una volta su mille corse, lo fanno. Quindi forse hai un bug che si verifica apparentemente a caso ed è difficile da riprodurre e quindi difficile da risolvere. Ugh!

O forse ... e questo è successo alla società per cui lavoro ... hai questi bug ma non sai che ci sono perché quasi nessuno dei tuoi clienti ha più di 4 CPU. Poi iniziano tutti ad acquistare scatole da 16 CPU ... e il tuo software esegue tanti thread quanti sono i core della CPU, quindi ora ci sono 4 volte più thread e improvvisamente stai andando in crash o ottenendo risultati sbagliati.

Quindi, di nuovo, al file. Per evitare che i thread si calpestino, ciascun thread deve acquisire un blocco sul file prima di scriverlo. Solo un thread può contenere il blocco alla volta, quindi solo un thread può scrivere sul file alla volta. Il thread mantiene il blocco fino a quando non viene eseguita la scrittura sul file, quindi rilascia il blocco in modo che un altro thread possa utilizzare il file.

Se i thread scrivono su file diversi, questo problema non si pone mai. Quindi questa è una soluzione: fai scrivere i tuoi thread su file diversi e combinali in seguito, se necessario. Ma questo non è sempre possibile; a volte, c'è solo una cosa.

Non deve essere file. Supponiamo che tu stia cercando di contare semplicemente il numero di occorrenze della lettera "A" in un gruppo di file diversi, un thread per file. Pensi, beh, ovviamente, avrò solo tutti i thread che incrementano la stessa posizione di memoria ogni volta che vedono una "A". Ma! Quando si va ad incrementare la variabile che mantiene il conteggio, il computer legge la variabile in un registro, incrementa il registro e quindi memorizza il valore nuovamente. Cosa succede se due thread leggono il valore allo stesso tempo, lo incrementano contemporaneamente e lo memorizzano nello stesso momento? Entrambi iniziano a, diciamo, 10, incrementano a 11, ripristinano 11. Quindi il contatore è 11 quando dovrebbe essere 12: hai perso un conteggio.

L'acquisizione dei blocchi può essere costosa, in quanto è necessario attendere fino a quando chiunque altro utilizza la risorsa. Questo è il motivo per cui il Global Interpreter Lock di Python rappresenta un collo di bottiglia per le prestazioni.Quindi potresti decidere di evitare l'uso di risorse condivise. Invece di usare una singola locazione di memoria per contenere il numero di "A" nei tuoi file, ogni thread mantiene il proprio conteggio, e tu li aggiungi tutti alla fine (simile alla soluzione che ho suggerito con i file, abbastanza stranamente) .

+0

Ok, questo ha senso, ma le serrature sto parlando di lock thread, non di file? Quindi cosa fanno? – MistahX

+1

@MistahX: No, bloccano qualunque cosa tu decida di bloccare. Li usi come meglio credi per impedire a più thread di fare la stessa cosa allo stesso tempo. Sono primitivi, costruisci da loro ciò di cui hai bisogno. Se si avvolge l'accesso a un file in un blocco, si sta effettivamente "bloccando" quel file. –

+1

Questo sta bloccando una risorsa, non il thread attuale. – Alan

9

In primo luogo, i blocchi sono progettati per proteggere le risorse; i thread non sono "bloccati" o "sbloccati"/acquisiscono/un blocco (su una risorsa) e/rilascia/un blocco (su una risorsa).

Lei ha ragione che si desidera thread per eseguire contemporaneamente il più possibile, ma diamo uno sguardo a questo:

y=10 

def doStuff(x): 
    global y 
    a = 2 * y 
    b = y/5 
    y = a + b + x 
    print y 

t1 = threading.Thread(target=doStuff, args=(8,)) 
t2 = threading.Thread(target=doStuff, args=(8,)) 
t1.start() 
t2.start() 
t1.join() 
t2.join() 

Ora, si potrebbe sapere che uno di questi fili potrebbero completare e stampare prima . Ci si aspetterebbe di vedere entrambe le uscite 30.

Ma potrebbero non farlo.

y è una risorsa condivisa e, in questo caso, i bit che leggono e scrivono su y fanno parte di quella che viene definita una "sezione critica" e dovrebbero essere protetti da un blocco. La ragione è che non si ottengono unità di lavoro: o thread può ottenere la CPU in qualsiasi momento.

Pensateci in questo modo:

T1 è felicemente l'esecuzione di codice e colpisce

a = 2 * y 

Ora T1 ha a = 20 e si ferma l'esecuzione per un po '. t2 diventa attivo mentre t1 attende più tempo della CPU. t2 esegue:

a = 2 * y 
b = y/5 
y = a + b + x 

a questo punto la variabile y globale = 30

t2 ferma ferma per un po 'e t1 riprende. esegue:

b = y/5 
y = a + b + x 

Dal y era il 30 quando b è stato impostato, b = 6 e y è ora impostato su 34.

l'ordine delle stampe non è deterministico come bene e si potrebbe ottenere la 30 prima o prima 34.

mezzo di una serratura si avrebbe:

global l 
l = threading.Lock() 
def doStuff(x): 
    global y 
    global l 
    l.acquire() 
    a = 2 * y 
    b = y/5 
    y = a + b + x 
    print y 
    l.release() 

Questo rende necessariamente questa sezione di codice lineare - solo thread alla volta. Ma se l'intero programma è sequenziale, non dovresti comunque usare i thread. L'idea è di aumentare la velocità in base alla percentuale di codice che è possibile eseguire al di fuori dei blocchi e correre in parallelo. Questo è (uno dei motivi) il motivo per cui l'utilizzo di thread su un sistema a 2 core non raddoppia le prestazioni per tutto.

il blocco stesso è anche una risorsa condivisa, ma deve essere: una volta che un thread acquisisce il blocco, tutti gli altri thread che tentano di acquisire lo/same/lock verranno bloccati finché non viene rilasciato. Una volta rilasciato, il primo thread per spostarsi in avanti e acquisire il blocco bloccherà tutti gli altri thread in attesa.

Speriamo che sia abbastanza per andare avanti!