web-dev-qa-db-de.com

Auf welchen CPU-Kernen laufen meine Python-Prozesse?

Die Einrichtung

Ich habe eine ziemlich komplexe Software in Python (auf einem Windows-PC) geschrieben. Meine Software startet im Wesentlichen zwei Python-Interpreter-Shells. Die erste Shell startet (ich nehme an), wenn Sie auf die main.py-Datei doppelklicken. In dieser Shell werden andere Threads auf folgende Weise gestartet:

    # 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()

Der Main_thread startet einen TCP_thread und einen UDP_thread. Obwohl es sich um separate Threads handelt, werden sie alle in einer einzigen Python-Shell ausgeführt.

Der Main_thread startet auch einen Unterprozess. Dies geschieht auf folgende Weise:

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

Aus der Python-Dokumentation verstehe ich, dass dieser Subprozess gleichzeitig (!) In einer separaten Python-Interpretersitzung/Shell ausführt. Der Main_thread in diesem Unterprozess ist vollständig meiner GUI gewidmet. Die GUI startet für ihre gesamte Kommunikation einen TCP_thread.

Ich weiß, dass die Dinge ein bisschen kompliziert werden. Deshalb habe ich das gesamte Setup in dieser Abbildung zusammengefasst:

 enter image description here


Ich habe mehrere Fragen zu diesem Setup. Ich werde sie hier aufführen:

Frage 1 [Gelöst]

Stimmt es, dass ein Python-Interpreter jeweils nur einen CPU-Kern verwendet, um alle Threads auszuführen? Wird der Python interpreter session 1 (aus der Abbildung) alle 3 Threads (Main_thread, TCP_thread und UDP_thread) auf einem CPU-Kern ausführen?

Antwort: Ja, das stimmt. Die GIL (Global Interpreter Lock) sorgt dafür, dass alle Threads gleichzeitig auf einem CPU-Kern laufen.

Frage 2 [Noch nicht gelöst]

Kann ich feststellen, um welchen CPU-Kern es sich handelt?

Frage 3 [Teilweise gelöst]

Für diese Frage vergessen wir Threads, konzentrieren uns aber auf den Subprozess Mechanismus in Python. Beim Starten eines neuen Unterprozesses muss ein neuer Python-Interpreter instance gestartet werden. Ist das richtig?

Antwort: Ja, das ist richtig. Zuerst gab es einige Verwirrung darüber, ob der folgende Code eine neue Python-Interpreterinstanz erstellen würde.

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

Das Problem wurde geklärt. Dieser Code startet tatsächlich eine neue Python-Interpreterinstanz.

Ist Python so intelligent, dass diese separate Python-Interpreterinstanz auf einem anderen CPU-Kern ausgeführt werden kann? Gibt es eine Möglichkeit, welche zu verfolgen, möglicherweise auch mit einigen sporadischen Druckaussagen?

Frage 4 [Neue Frage]

Die Community-Diskussion warf eine neue Frage auf. Anscheinend gibt es zwei Ansätze, wenn ein neuer Prozess (innerhalb einer neuen Python-Interpreterinstanz) gestartet wird:

    # 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,))

Der zweite Ansatz hat den offensichtlichen Nachteil, dass er nur auf eine Funktion abzielt - während ich ein neues Python-Skript öffnen muss. Sind beide Ansätze in ihrem Ergebnis ähnlich?

31
K.Mulier

F: Stimmt es, dass ein Python Interpreter jeweils nur einen CPU-Kern verwendet, um alle Threads auszuführen?

GIL und CPU-Affinität sind nicht verwandte Konzepte. GIL kann ohnehin während des Blockierens von E/A-Vorgängen freigegeben werden, lange CPU-intensive Berechnungen innerhalb einer C-Erweiterung.

Wenn ein Thread auf GIL blockiert ist; Es befindet sich wahrscheinlich nicht auf einem CPU-Kern und daher kann man mit Recht sagen, dass reiner Python Multithreading-Code bei der CPython-Implementierung nur jeweils einen CPU-Kern verwenden darf.

F: Mit anderen Worten, wird der Python Interpreter Session 1 (aus der Abbildung) alle 3 Threads (Main_thread) ausführen , TCP_thread und UDP_thread) auf einem CPU-Kern?

Ich glaube nicht, dass CPython die CPU-Affinität implizit verwaltet. Es ist wahrscheinlich, dass der OS-Scheduler entscheidet, wo ein Thread ausgeführt wird. Python - Threads werden über echte Betriebssystem-Threads implementiert.

F: Oder kann der Python Interpreter sie auf mehrere Kerne verteilen?

So ermitteln Sie die Anzahl der verwendbaren CPUs:

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

Auch hier hängt es nicht vom Python Interpreter ab, ob Threads auf verschiedenen CPUs geplant sind oder nicht.

F: Angenommen, die Antwort auf Frage 1 lautet "mehrere Kerne". Kann ich nachverfolgen, auf welchem ​​Kern jeder Thread ausgeführt wird, vielleicht mit einige sporadische Druckaussagen? Wenn die Antwort auf Frage 1 "nur ein Kern" lautet, kann ich dann nachverfolgen, um welchen Kern es sich handelt?

Ich stelle mir vor, eine bestimmte CPU kann sich von einem Zeitfenster zum anderen ändern. Sie könnten sehen Sie sich etwas wie /proc/<pid>/task/<tid>/status Auf alten Linux-Kerneln an . Auf meinem Computer kann task_cpu Von /proc/<pid>/stat Oder /proc/<pid>/task/<tid>/stat gelesen werden:

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

Sehen Sie für eine aktuelle portable Lösung, ob psutil solche Informationen verfügbar macht.

Sie können den aktuellen Prozess auf eine Reihe von CPUs beschränken:

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

F: Bei dieser Frage vergessen wir die Threads, konzentrieren uns aber auf den Unterprozessmechanismus in Python. Das Starten eines neuen Unterprozesses impliziert das Starten einer neuen Python Interpretersitzung/Shell. Ist das richtig?

Ja. Das Modul subprocess erstellt neue Betriebssystemprozesse. Wenn Sie python executable ausführen, wird ein neuer Python Interpeter gestartet. Wenn Sie ein Bash-Skript ausführen, wird kein neuer Python Interpreter erstellt, d. H Wenn Sie bash executable ausführen, wird kein neuer Python interpreter/session/etc.

F: Vorausgesetzt, es ist korrekt, wird Python) klug genug sein, um diese separate Interpretersitzung auf einem anderen Computer auszuführen CPU-Kern Gibt es eine Möglichkeit, dies zu verfolgen, möglicherweise auch mit einigen sporadischen Druckanweisungen?

Siehe oben (d. H. Das Betriebssystem entscheidet, wo der Thread ausgeführt wird, und es könnte eine Betriebssystem-API geben, die angibt, wo der Thread ausgeführt wird).

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

multiprocessing.Process Erstellt auch einen neuen Betriebssystemprozess (der einen neuen Python Interpreter) ausführt).

In Wirklichkeit ist mein Unterprozess eine andere Datei. Also wird dieses Beispiel für mich nicht funktionieren.

Python verwendet Module, um den Code zu organisieren. Wenn Ihr Code in another_file.py Ist, dann import another_file In Ihrem Hauptmodul und übergeben Sie another_file.foo An multiprocessing.Process.

Wie würden Sie es dennoch mit p = subprocess.Popen (..) vergleichen? Ist es wichtig, ob ich den neuen Prozess (oder 'Python-Interpreter-Instanz') mit subprocess.Popen (..) oder multiprocessing.Process (..) starte?

multiprocessing.Process() wird wahrscheinlich über subprocess.Popen() implementiert. multiprocessing stellt eine API bereit, die der threading-API ähnelt, und abstrahiert Details der Kommunikation zwischen python process (how Python) Objekte werden serialisiert, um zwischen Prozessen gesendet zu werden.

Wenn es keine CPU-intensiven Aufgaben gibt, können Sie Ihre GUI- und E/A-Threads in einem einzigen Prozess ausführen. Wenn Sie eine Reihe von CPU-intensiven Aufgaben haben, um mehrere CPUs gleichzeitig zu nutzen, verwenden Sie entweder mehrere Threads mit C-Erweiterungen wie lxml, regex, numpy (oder Ihre eigenen) Eine mit Cython ) erstellte Methode kann GIL bei langen Berechnungen freigeben oder in separate Prozesse auslagern (eine einfache Möglichkeit ist die Verwendung eines Prozesspools, wie er von ) bereitgestellt wird concurrent.futures ).

F: Die Community-Diskussion hat eine neue Frage aufgeworfen. Es gibt anscheinend zwei Ansätze, um einen neuen Prozess zu erzeugen (innerhalb einer neuen Python Interpreter-Instanz)):

# 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,))

"Approach 1 (a)" ist unter POSIX falsch (funktioniert möglicherweise unter Windows). Verwenden Sie aus Gründen der Portabilität "Approach 1 (b)", es sei denn, Sie wissen, dass Sie cmd.exe Benötigen (übergeben Sie in diesem Fall eine Zeichenfolge, um sicherzustellen, dass die korrekte Kommandozeile maskiert ist) benutzt).

Der zweite Ansatz hat den offensichtlichen Nachteil, dass er nur auf eine Funktion abzielt - während ich ein neues Python Skript öffnen muss. Wie auch immer, sind beide Ansätze in dem, was sie erreichen, ähnlich?

subprocess erstellt neue Prozesse, any Prozesse, z. B. könnten Sie ein Bash-Skript ausführen. multprocessing wird verwendet, um Python Code in einem anderen Prozess auszuführen. Es ist flexibler, Import a Python = Modul und führen Sie seine Funktion als ein Skript aus. Siehe Aufruf python Skript mit Eingabe mit in einem python Skript mit Unterprozess .

25
jfs

Da Sie das threading-Modul verwenden, das auf thread aufgebaut ist. Wie aus der Dokumentation hervorgeht, wird die POSIX-Thread-Implementierung pthread Ihres Betriebssystems verwendet. 

  1. Die Threads werden vom Betriebssystem statt vom Python-Interpreter verwaltet. Die Antwort hängt also von der pthread-Bibliothek in Ihrem System ab. CPython verwendet jedoch GIL, um zu verhindern, dass mehrere Threads Python-Bytecodes gleichzeitig ausführen. Sie werden also sequenziert. Trotzdem können sie in verschiedene Kerne unterteilt werden, was von Ihren pthread-Bibliotheken abhängig ist.
  2. Verwenden Sie einfach einen Debugger und hängen Sie ihn an Ihre python.exe an. Zum Beispiel der GDB-Threadbefehl .
  3. Ähnlich wie bei Frage 1 wird der neue Prozess von Ihrem Betriebssystem verwaltet und läuft wahrscheinlich auf einem anderen Kern. Verwenden Sie den Debugger oder einen anderen Prozessmonitor, um es anzuzeigen. Weitere Informationen finden Sie in der CreatProcess() Dokumentation Seite .
3
gdlmx

1, 2: Sie haben drei echte Threads, aber in CPython sind sie durch GIL begrenzt. Wenn Sie also Python mit reinem Python ausführen, wird die CPU-Auslastung so angezeigt, als würde nur ein Kern verwendet.

3: Wie gesagt, es ist Sache des Betriebssystems, einen Kern auszuwählen, auf dem ein Thread ausgeführt werden soll Wenn Sie jedoch wirklich eine Steuerung benötigen, können Sie die Prozess- oder Thread-Affinität mithilfe der ctypes-API festlegen. Da Sie unter Windows sind, würde es so aussehen:

# 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)

Ich benutze hier privat Popen._handle zur Vereinfachung. Der saubere Weg wäre OpenProcess(p.tid) usw. 

Und ja, subprocess führt Python wie alles andere in einem anderen neuen Prozess aus. 

1
robyschek