2014-11-11 29 views
7

Sono consapevole che i thread Python possono eseguire solo bytecode uno alla volta, quindi perché la libreria threading fornisce i blocchi? Presumo che le condizioni di gara non possano verificarsi se solo un thread è in esecuzione alla volta.Perché Python fornisce meccanismi di blocco se è soggetto a un GIL?

La libreria fornisce blocchi, condizioni e semafori. L'unico scopo è sincronizzare l'esecuzione?

Aggiornamento:

ho eseguito un piccolo esperimento:

from threading import Thread 
from multiprocessing import Process 

num = 0 

def f(): 
    global num 
    num += 1 

def thread(func): 
    # return Process(target=func) 
    return Thread(target=func) 


if __name__ == '__main__': 
    t_list = [] 
    for i in xrange(1, 100000): 
     t = thread(f) 
     t.start() 
     t_list.append(t) 

    for t in t_list: 
     t.join() 

    print num 

Fondamentalmente ho dovuto iniziare le discussioni 100k e incrementato di 1. Il risultato riscontrato è 99993.

a) Come può il risultato non essere 99999 se c'è una sincronizzazione GIL ed evitare le condizioni di gara? b) È anche possibile avviare thread 100k OS?

Update 2, dopo aver visto le risposte:

Se la GIL in realtà non fornisce un modo per eseguire una semplice operazione come incrementare atomicamente, qual è lo scopo di avere lì? Non aiuta con problemi di concorrenza brutti, quindi perché è stato messo in atto? Ho sentito casi d'uso per estensioni C, qualcuno può esemplificare questo?

+1

Il GIL è lì per proteggere l'interprete Python stesso da problemi di concorrenza, piuttosto che il codice che scrivi. È davvero un dettaglio di implementazione di CPython e non dovresti fare affidamento sul suo comportamento nel tuo codice, anche se non è probabile che vada via in qualunque momento presto. – dano

risposta

8

Il GIL sincronizza le operazioni bytecode. È possibile eseguire un solo codice byte contemporaneamente. Ma se si ha un'operazione che richiede più di un bytecode, è possibile cambiare i thread tra i bytecode. Se è necessario che l'operazione sia atomica, è necessario eseguire la sincronizzazione al di sopra e al di là di GIL.

Per esempio, incrementando un intero non è un singolo bytecode:

>>> def f(): 
... global num 
... num += 1 
... 
>>> dis.dis(f) 
    3   0 LOAD_GLOBAL    0 (num) 
       3 LOAD_CONST    1 (1) 
       6 INPLACE_ADD 
       7 STORE_GLOBAL    0 (num) 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE 

Qui ci sono voluti quattro bytecode per implementare num += 1. Il GIL non garantisce che x venga incrementato atomicamente. Il tuo esperimento mostra il problema: hai perso gli aggiornamenti perché i thread sono passati tra LOAD_GLOBAL e STORE_GLOBAL.

Lo scopo del GIL è di garantire che i conteggi di riferimento sugli oggetti Python siano incrementati e decrementati atomicamente. Non è pensato per aiutarti con le tue strutture dati.

3

Il threading nativo di Python funziona a livello di byte. Cioè, dopo ogni bytecode (beh, in realtà, credo che il numero di bytecode sia configurabile), un thread può dare controllo ad un altro thread.

Qualsiasi operazione su una risorsa condivisa che non è un bytecode richiede un blocco. E anche se una determinata operazione è, in una determinata versione di CPython, un singolo bytecode, questo potrebbe non essere il caso in ogni versione di ogni interprete, quindi è meglio usare comunque un blocco.

Lo stesso motivo per cui sono necessari i blocchi, in realtà, tranne che a livello di VM piuttosto che a livello di hardware.