Perché Python è stato scritto con GIL?


112

Il blocco dell'interprete globale (GIL) sembra essere spesso citato come uno dei motivi principali per cui il threading e simili è un tocco complicato in Python - il che solleva la domanda "Perché è stato fatto in primo luogo?"

Non essendo un programmatore, non ho idea del perché potrebbe essere: qual è la logica dietro l'inserimento del GIL?


10
L' articolo di Wikipedia afferma che "la GIL può essere una barriera significativa al parallelismo, un prezzo pagato per avere il dinamismo della lingua" , e continua dicendo che "I motivi per impiegare un tale blocco includono: aumento della velocità dei programmi a thread singolo (nessuna necessità di acquisire o rilasciare blocchi su tutte le strutture di dati separatamente) e una facile integrazione di librerie C che di solito non sono thread-safe. "
Robert Harvey,

3
@RobertHarvey, il dinamismo non ha nulla a che fare con esso. Il problema è la mutazione.
dan_waterworth,


1
Non posso fare a meno di sentire che, come la mancanza di numeri numerici non firmati di Java, era destinato a impedire alle persone che non sanno cosa stanno facendo di spararsi ai piedi. Purtroppo, chi non sanno quello che stanno facendo ottiene un linguaggio carente, che è un vero peccato perché rocce Python in tanti altri modi
di base

1
@Basic ci deve essere un modo standard per gestire gli array di byte in Java (non lo uso da molto tempo) per eseguire la crittografia matematica. Python (per esempio) non ha numeri con segno, ma non proverei nemmeno a fare operazioni bit per bit perché ci sono modi migliori.
Nick T

Risposte:


105

Esistono diverse implementazioni di Python, ad esempio CPython, IronPython, RPython, ecc.

Alcuni hanno un GIL, altri no. Ad esempio, CPython ha il GIL:

Da http://en.wikipedia.org/wiki/Global_Interpreter_Lock

Le applicazioni scritte in linguaggi di programmazione con un GIL possono essere progettate per utilizzare processi separati per ottenere il pieno parallelismo, poiché ogni processo ha il suo interprete e a sua volta ha il suo GIL.

Vantaggi del GIL

  • Maggiore velocità dei programmi a thread singolo.
  • Facile integrazione di librerie C che di solito non sono thread-safe.

Perché Python (CPython e altri) usa GIL

In CPython, il blocco dell'interprete globale, o GIL, è un mutex che impedisce a più thread nativi di eseguire contemporaneamente bytecode Python. Questo blocco è necessario principalmente perché la gestione della memoria di CPython non è thread-safe.

Il GIL è controverso perché impedisce ai programmi CPython multithread di sfruttare appieno i sistemi multiprocessore in determinate situazioni. Si noti che operazioni potenzialmente bloccanti o di lunga durata, come I / O, elaborazione delle immagini e crunching dei numeri NumPy, avvengono al di fuori di GIL. Pertanto, è solo nei programmi multithread che trascorrono molto tempo all'interno del GIL, interpretando il bytecode CPython, che il GIL diventa un collo di bottiglia.

Python ha un GIL rispetto al blocco a grana fine per diversi motivi:

  • È più veloce nel caso a thread singolo.

  • È più veloce nel caso multi-thread per i programmi i / o associati.

  • È più veloce nel caso multi-thread per i programmi cpu associati che svolgono il loro intenso lavoro di calcolo nelle librerie C.

  • Semplifica la scrittura delle estensioni C: non ci sarà alcun cambio di thread Python se non nel caso in cui lo consenti (ad esempio tra le macro Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS).

  • Rende più semplice il wrapping delle librerie C. Non devi preoccuparti della sicurezza del thread. Se la libreria non è thread-safe, tieni semplicemente bloccato GIL mentre lo chiami.

Il GIL può essere rilasciato da estensioni C. La libreria standard di Python rilascia GIL attorno a ogni chiamata di I / O bloccante. Pertanto, il GIL non ha conseguenze per le prestazioni dei server associati i / o. Puoi quindi creare server di rete in Python usando processi (fork), thread o I / O asincroni e GIL non ti ostacolerà.

Le librerie numeriche in C o Fortran possono essere chiamate allo stesso modo con il rilascio di GIL. Mentre l'estensione C è in attesa del completamento di una FFT, l'interprete eseguirà altri thread Python. In questo caso, un GIL è quindi più facile e veloce del bloccaggio a grana fine. Ciò costituisce la maggior parte del lavoro numerico. L'estensione NumPy rilascia GIL quando possibile.

I thread di solito sono un cattivo modo di scrivere la maggior parte dei programmi server. Se il carico è basso, il fork è più semplice. Se il carico è elevato, è meglio l'I / O asincrono e la programmazione guidata dagli eventi (ad es. Utilizzando il framework Twisted di Python). L'unica scusa per l'utilizzo dei thread è la mancanza di os.fork su Windows.

Il GIL è un problema se, e solo se, stai facendo un lavoro ad alta intensità di CPU in Python puro. Qui puoi ottenere un design più pulito usando i processi e il passaggio dei messaggi (ad esempio mpi4py). Esiste anche un modulo di "elaborazione" nel negozio di formaggi Python, che fornisce ai processi la stessa interfaccia dei thread (ovvero sostituisce il threading. Filettato con processing.Process).

I thread possono essere utilizzati per mantenere la reattività di una GUI indipendentemente dal GIL. Se il GIL compromette le tue prestazioni (vedi la discussione sopra), puoi lasciare che il tuo thread generi un processo e attendere che finisca.


52
Mi sembra un'uva acida. Python non è in grado di eseguire correttamente i thread, quindi è possibile individuare i motivi per cui i thread sono inutili o addirittura dannosi. "Se il carico è basso, il fork è più facile", sul serio? E il GIL è "più veloce" per tutti quei casi solo se insisti nell'usare GC per il conteggio dei riferimenti.
Michael Borgwardt,

9
s/RPython/PyPy/g. @MichaelBorgwardt Fornire ragioni per GIL è un po 'il punto della domanda, no? Anche se sarei d'accordo sul fatto che alcuni dei contenuti di questa risposta (vale a dire la discussione di alternative) non siano pertinenti. E nel bene o nel male, il ricontattamento è ora quasi impossibile da eliminare: è profondamente radicato nell'intera API e nella base di codice; è quasi impossibile liberarsene senza riscrivere metà del codice e rompere tutto il codice esterno.

10
Non dimenticare la multiprocessinglibreria - standard dal 2.6. I suoi pool di lavoratori sono un'astrazione super liscia per alcuni semplici tipi di parallelismo.
Sean McSomething,

8
@alcalde Solo se non sai cosa stai facendo e / o non vuoi che i tuoi thread possano lavorare in modo cooperativo / comunicare. Altrimenti, è un dolore reale nella parte posteriore, soprattutto considerando il sovraccarico del lancio di un nuovo processo su alcuni sistemi operativi. Abbiamo server con 32 core, quindi per utilizzarli completamente in CPython avrei bisogno di 32 processi. Non è una "buona soluzione", è un trucco per aggirare le inadeguatezze di CPython.
Basic

8
Il fatto che i thread esistano su piattaforme diverse da Windows dovrebbe essere la prova sufficiente che il fork non è adeguato in ogni situazione.
zneak,

42

Prima di tutto: Python non ha un GIL. Python è un linguaggio di programmazione. Un linguaggio di programmazione è un insieme di regole e restrizioni matematiche astratte. Non c'è nulla nella specifica del linguaggio Python che dice che ci deve essere un GIL.

Esistono diverse implementazioni di Python. Alcuni hanno un GIL, altri no.

Una semplice spiegazione per avere un GIL è che scrivere codice simultaneo è difficile. Posizionando un lucchetto gigante attorno al codice, lo costringi a funzionare sempre in serie. Problema risolto!

In CPython, in particolare, un obiettivo importante è semplificare l'estensione dell'interprete con plugin scritti in C. Anche in questo caso, scrivere codice simultaneo è difficile, quindi garantendo che non ci sarà concorrenza, sarà più facile scrivere estensioni per l'interprete. Inoltre, molte di queste estensioni sono solo sottili involucri attorno alle librerie esistenti che potrebbero non essere state scritte pensando alla concorrenza.


6
Questo è lo stesso argomento della mancanza di Java di tipi numerici non firmati - gli sviluppatori pensano che tutti gli altri siano più stupidi di quanto non siano ...
Basic

1
@Basic - che ci crediate o no, anche quando non siete veramente, davvero stupidi, si scopre che avere un linguaggio che rende ipotesi semplificative che significa che non pensate a certe cose per farle funzionare è ancora utile cosa. CPython è ottimo per alcune cose, tra cui semplici applicazioni multithread (dove il programma è legato all'IO, che molte sono, e quindi il GIL non ha importanza), perché le decisioni di progettazione che hanno reso GIL la soluzione migliore rendono anche più semplice la programmazione di tali applicazioni , in particolare il fatto che supporta le operazioni atomiche sulle raccolte .
Jules,

@Jules Sì, è molto utile fino a quando non hai bisogno di quelle funzionalità. La soluzione "preferita" di cpython di "basta scriverla in un'altra lingua come c ++" significa quindi perdere ogni singolo vantaggio di Python. Se stai scrivendo metà del tuo codice in c ++, allora perché iniziare da Python? Certo, per piccoli progetti API / colla è semplice e veloce e per ETL non è secondo a nessuno, ma non è adatto a tutto ciò che richiede un sollevamento pesante. Come usare Java per parlare con l'hardware ... È quasi comico i cerchi che devi saltare.
Base

16

Qual è lo scopo di un GIL?

La documentazione CAPI ha questo da dire sull'argomento:

L'interprete Python non è completamente thread-safe. Per supportare i programmi Python multi-thread, esiste un blocco globale, chiamato blocco dell'interprete globale o GIL, che deve essere trattenuto dal thread corrente prima che possa accedere in sicurezza agli oggetti Python. Senza il blocco, anche le operazioni più semplici potrebbero causare problemi in un programma multi-thread: ad esempio, quando due thread incrementano simultaneamente il conteggio dei riferimenti dello stesso oggetto, il conteggio dei riferimenti potrebbe finire per essere incrementato solo una volta anziché due volte.

In altre parole, il GIL impedisce la corruzione dello stato. I programmi Python non dovrebbero mai produrre un errore di segmentazione, poiché sono consentite solo operazioni sicure con la memoria. Il GIL estende questa garanzia ai programmi multi-thread.

Quali sono le alternative?

Se lo scopo del GIL è proteggere lo stato dalla corruzione, allora una ovvia alternativa è quella di bloccare un grano molto più fine; forse a livello di oggetto. Il problema è che, sebbene sia stato dimostrato che aumenta le prestazioni dei programmi multi-thread, ha più sovraccarico e di conseguenza i programmi single-thread ne risentono.


2
Sarebbe bello consentire a un utente di eseguire un programma con un'opzione di interpretazione che sostituisce il gil per un blocco a grana fine e in qualche modo sapere, in modo di sola lettura, se il processo corrente è stato sollevato con o senza gil.
Luis Masuelli,

Nonostante GIL sono riuscito a produrre un errore di segmentazione in un programma multithread a causa dell'uso incauto del modulo pyodbc. Quindi "non dovrebbe mai produrre un errore di segmentazione" è un errore.
Muposat,
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.