2016-04-22 33 views
28

Il setupSu quali core della CPU sono in esecuzione i miei processi Python?

ho scritto un pezzo piuttosto complessa di software in Python (su un PC Windows). Il mio software inizia fondamentalmente due shell per interpreti Python. La prima shell si avvia (suppongo) quando si fa doppio clic sul file main.py. Da quel guscio, altri thread vengono avviati nel modo seguente:

# Start TCP_thread 
    TCP_thread = threading.Thread(name = 'TCP_loop', target = TCP_loop, args = (TCPsock,)) 
    TCP_thread.start() 

    # Start UDP_thread 
    UDP_thread = threading.Thread(name = 'UDP_loop', target = UDP_loop, args = (UDPsock,)) 
    TCP_thread.start() 

Il Main_thread avvia un TCP_thread e UDP_thread. Sebbene si tratti di thread separati, vengono eseguiti tutti all'interno di una singola shell Python.

Main_thread avvia anche un sottoprocesso. Questo viene fatto nel modo seguente:

p = subprocess.Popen(['python', mySubprocessPath], shell=True) 

Dalla documentazione Python, ho capito che questo è in esecuzione il sottoprocesso contemporaneamente in una sessione interprete Python/shell separato (!). Lo Main_thread in questo sottoprocesso è completamente dedicato alla mia GUI. La GUI avvia un TCP_thread per tutte le sue comunicazioni.

So che le cose diventano un po 'complicate. Perciò ho riassunto l'intero setup in questa figura:

enter image description here


Ho molte domande riguardanti questa configurazione. Io li elenco qui:

Domanda 1 [Risolto]

E 'vero che un interprete Python utilizza un solo core CPU alla volta per eseguire tutti i fili? In altre parole, lo Python interpreter session 1 (dalla figura) eseguirà tutti i 3 thread (Main_thread, TCP_thread e UDP_thread) su un core della CPU?

Risposta: sì, questo è vero. Il GIL (Global Interpreter Lock) assicura che tutti i thread vengano eseguiti su un core della CPU alla volta.

Domanda 2 [Non ancora risolto]

Ho un modo per tenere traccia che core della CPU è?

Domanda 3 [parzialmente risolto]

Per questa domanda ci dimentichiamo di discussioni, ma ci concentriamo sulla sottoprocesso meccanismo in Python. L'avvio di un nuovo sottoprocesso implica l'avvio di un nuovo interprete Python istanza. È corretto?

Risposta: Sì, è corretto.In un primo momento ci fu una certa confusione sul fatto che il seguente codice dovrebbe creare una nuova istanza dell'interprete Python:

p = subprocess.Popen(['python', mySubprocessPath], shell = True) 

La questione è stata chiarita. Questo codice avvia effettivamente una nuova istanza dell'interprete Python.

Will Python sarà abbastanza intelligente da rendere l'istanza di interprete Python separata eseguita su un core della CPU diverso? C'è un modo per tenere traccia di quale, forse con alcune dichiarazioni di stampa sporadiche?

Domanda 4 [Nuova domanda]

La discussione comunità ha sollevato una nuova domanda. Ci sono apparentemente due approcci quando generando un nuovo processo (all'interno di una nuova istanza interprete Python):

# Approach 1(a) 
    p = subprocess.Popen(['python', mySubprocessPath], shell = True) 

    # Approach 1(b) (J.F. Sebastian) 
    p = subprocess.Popen([sys.executable, mySubprocessPath]) 

    # Approach 2 
    p = multiprocessing.Process(target=foo, args=(q,)) 

Il secondo approccio ha lo svantaggio evidente che esso si rivolge solo una funzione - che, ho bisogno di aprire un nuovo script Python . Ad ogni modo, entrambi gli approcci sono simili in ciò che ottengono?

+0

https://docs.python.org/2/library/multiprocessing.html – mootmoot

+3

Penso che dovresti mettere in discussione il motivo per cui ti preoccupi di quali core fisici i thread eseguono. In genere, il sistema operativo sposta i thread tra le CPU disponibili nel sistema in base a vari fattori. C'è qualche ragione particolare per cui vuoi monitorare e/o interferire con questo processo? – Dolda2000

+0

Buona domanda :-). Sì, credo di avere una ragione valida. Sto costruendo un sistema di acquisizione dati in Python, che legge i miei dati del microcontrollore (come gli ingressi analogici, ...) e mostra grafici in diretta nella mia GUI. Finché i dati in arrivo sono limitati, a nessuno interessa il multiprocessing. Ma una volta diventato molto veloce, voglio avere il controllo. Forse posso fare in modo che alcune parti a bassa latenza del mio software Python girino su un core CPU dedicato che non uso per nient'altro, assicurando quindi un'alta reattività. –

risposta

16

D: E 'vero che un interprete Python utilizza un solo core CPU alla volta per eseguire tutti i fili?

N. GIL e affinità CPU sono concetti non correlati. GIL può essere rilasciato durante il blocco delle operazioni di I/O, con lunghi calcoli intensivi della CPU all'interno di un'estensione C.

Se un thread è bloccato su GIL; non è probabilmente su alcun core della CPU e quindi è giusto dire che il codice di multithreading puro Python può utilizzare solo un core della CPU alla volta nell'implementazione di CPython.

Q: In altre parole, sarà l'interprete Python sessione 1 (dalla figura) eseguire tutti i 3 fili (Main_thread, TCP_thread e UDP_thread) su un core CPU?

Non penso che CPython gestisca implicitamente l'affinità della CPU. È probabile che si basi sullo scheduler del SO per scegliere dove eseguire un thread. I thread Python sono implementati sopra i veri thread del sistema operativo.

D: O è l'interprete Python in grado di diffonderli su più core?

Per scoprire il numero di CPU utilizzabili:

>>> import os 
>>> len(os.sched_getaffinity(0)) 
16 

Nuovamente, anche fili sono previste su diverse CPU non dipende interprete Python.

D: Supponiamo che la risposta alla questione sub 1 'più core', ho un modo per tenere traccia su cui nucleo ciascun thread è in esecuzione, magari con alcune dichiarazioni di stampa sporadici? Se la risposta alla domanda 1 è "solo un nucleo", ho un modo per tenere traccia di quale è?

Immagino che una CPU specifica possa passare da una fascia oraria a un'altra. Potresti look at something like /proc/<pid>/task/<tid>/status on old Linux kernels. Sulla mia macchina, task_cpu can be read from /proc/<pid>/stat or /proc/<pid>/task/<tid>/stat:

>>> open("/proc/{pid}/stat".format(pid=os.getpid()), 'rb').read().split()[-14] 
'4' 

Per una soluzione portatile di corrente, vedere se psutil espone tali informazioni.

è possibile limitare il processo in corso a una serie di CPU:

os.sched_setaffinity(0, {0}) # current process on 0-th core 

D: Per questa domanda ci dimentichiamo di discussioni, ma ci concentriamo sul meccanismo sottoprocesso in Python. L'avvio di un nuovo sottoprocesso implica l'avvio di una nuova sessione/shell dell'interprete Python. È corretto?

Sì. Il modulo subprocess crea nuovi processi del sistema operativo. Se si esegue l'eseguibile python, viene avviato un nuovo interpeter Python. Se si esegue uno script bash, non viene creato alcun nuovo interprete Python, cioè l'esecuzione dell'eseguibile bash non avvia un nuovo interprete/sessione Python/ecc.

D: Supponendo che sia corretto, sarà Python essere abbastanza intelligente per fare che separano corsa sessione interprete su un core della CPU diversa? C'è un modo per tenere traccia di ciò, magari con alcune dichiarazioni di stampa sporadiche?

Vedere sopra (ad esempio, SO decide dove eseguire il thread e potrebbe esserci API del sistema operativo che espone dove viene eseguito il thread).

multiprocessing.Process(target=foo, args=(q,)).start()

multiprocessing.Process crea anche un nuovo processo di sistema operativo (che viene eseguito un nuovo interprete Python).

In realtà, il mio sottoprocesso è un altro file. Quindi questo esempio non funzionerà per me.

Python utilizza i moduli per organizzare il codice. Se il tuo codice è another_file.py quindi import another_file nel modulo principale e passa another_file.foo a multiprocessing.Process.

Tuttavia, come lo si confronta con p = subprocess.Popen (..)? Importa se avvio il nuovo processo (o dovrei dire "istanza dell'interprete python") con subprocess.Popen (..) contro multiprocessing.Process (..)?

multiprocessing.Process() è probabilmente implementato in cima subprocess.Popen(). multiprocessing fornisce API che è simile all'API threading e astrae i dettagli della comunicazione tra i processi python (come gli oggetti Python sono serializzati per essere inviati tra processi).

Se non ci sono attività a uso intensivo della CPU, è possibile eseguire la GUI e i thread I/O in un unico processo. Se si dispone di una serie di attività CPU intensive per utilizzare più CPU contemporaneamente, utilizzare più thread con estensioni C come lxml, regex, numpy (o il proprio creato utilizzando Cython) che può rilasciare GIL durante lunghi calcoli o scaricarli. in processi separati (un modo semplice è utilizzare un pool di processi come previsto da concurrent.futures).

D: La discussione comunità sollevato una nuova domanda. Ci sono apparentemente due approcci quando generando un nuovo processo (all'interno di una nuova istanza dell'interprete Python):

# Approach 1(a) 
p = subprocess.Popen(['python', mySubprocessPath], shell = True) 

# Approach 1(b) (J.F. Sebastian) 
p = subprocess.Popen([sys.executable, mySubprocessPath]) 

# Approach 2 
p = multiprocessing.Process(target=foo, args=(q,)) 

"Approccio 1 (a)" è sbagliato su POSIX (anche se può funzionare su Windows). Per la portabilità, utilizzare "Approccio 1 (b)" a meno che non si sappia che è necessario cmd.exe (passare una stringa in questo caso, per assicurarsi che venga utilizzata l'escape della riga di comando corretta).

Il secondo approccio ha l'ovvio svantaggio che mira solo a una funzione - mentre ho bisogno di aprire un nuovo script Python. Ad ogni modo, entrambi gli approcci sono simili in ciò che ottengono?

subprocess crea nuovi processi, eventuali processi per esempio, è possibile eseguire uno script bash. multprocessing viene utilizzato per eseguire il codice Python in un altro processo. È più flessibile a importare un modulo Python ed eseguire la sua funzione piuttosto che eseguirlo come script. Vedi Call python script with input with in a python script using subprocess.

3

Poiché si sta utilizzando il modulo threading che si accumula su thread. Come suggerisce la documentazione, utilizza la '' implementazione del thread POSIX '' pthread del sistema operativo.

  1. I thread sono gestiti dal sistema operativo anziché dall'interprete Python. Quindi la risposta dipenderà dalla libreria pthread nel tuo sistema. Tuttavia, CPython utilizza GIL per impedire a più thread di eseguire bytecode Python simulatamente. Quindi saranno sequenziati. Ma ancora possono essere separati in diversi core, che dipende dalle tue librerie pthread.
  2. Utilizza in modo semplice un debugger e collegalo al tuo python.exe. Ad esempio lo GDB thread command.
  3. Simile alla domanda 1, il nuovo processo è gestito dal sistema operativo e probabilmente in esecuzione su un core diverso. Usa il debugger o qualsiasi monitor di processo per vederlo. Per ulteriori dettagli, consultare la documentazione CreatProcess()page.
1

1, 2: Hai tre fili reali, ma in CPython si è limitati dalla GIL, quindi, ammesso che siano in esecuzione pitone puro, il codice vedrete l'utilizzo della CPU, come se un solo core utilizzato.

3: Come detto gdlmx tocca a OS di scegliere un nucleo di eseguire un thread su, ma se si ha realmente bisogno di controllo, è possibile impostare l'affinità di processo o thread che utilizza API nativa tramite ctypes. Dal momento che siete su Windows, sarebbe come questo:

# This will run your subprocess on core#0 only 
p = subprocess.Popen(['python', mySubprocessPath], shell = True) 
cpu_mask = 1 
ctypes.windll.kernel32.SetProcessAffinityMask(p._handle, cpu_mask) 

Io uso qui privata Popen._handle per simplicty. Il modo pulito sarebbe OpenProcess(p.tid) ecc.

E sì, subprocess esegue python come tutto il resto in un altro nuovo processo.

+0

(3) funziona perfettamente. Senza di esso, un semplice ciclo occupato a thread singolo viene lanciato su tutti i core; con quel codice, è permanentemente sul core # 0.Non ho idea se sia mai utile, ma forse ci sono alcuni motivi relativi alla cache per mantenere un thread critico su un singolo core. – max