Was sind die Unterschiede zwischen den Threading- und Multiprocessing-Modulen?

140

Ich lerne, wie ich die threadingund die multiprocessingModule in Python verwende, um bestimmte Operationen parallel auszuführen und meinen Code zu beschleunigen.

Ich finde es schwierig (vielleicht weil ich keinen theoretischen Hintergrund dazu habe) zu verstehen, was der Unterschied zwischen einem threading.Thread()Objekt und einem ist multiprocessing.Process().

Außerdem ist mir nicht ganz klar, wie ich eine Warteschlange von Jobs instanziieren und nur 4 (zum Beispiel) parallel ausführen kann, während die anderen darauf warten, dass Ressourcen freigegeben werden, bevor sie ausgeführt werden.

Ich finde die Beispiele in der Dokumentation klar, aber nicht sehr erschöpfend; Sobald ich versuche, die Dinge etwas zu komplizieren, erhalte ich viele seltsame Fehler (wie eine Methode, die nicht eingelegt werden kann, und so weiter).

Wann sollte ich die Module threadingund verwenden multiprocessing?

Können Sie mich mit einigen Ressourcen verknüpfen, in denen die Konzepte hinter diesen beiden Modulen erläutert werden und wie sie für komplexe Aufgaben richtig verwendet werden können?

Lucaceron
quelle
Es gibt noch mehr, es gibt auch das ThreadModul ( _threadin Python 3.x genannt). Um ehrlich zu sein, habe ich die Unterschiede selbst nie verstanden ...
Keine Ahnung,
3
@ Dunno: Wie in der Thread/ _threadDokumentation ausdrücklich angegeben, handelt es sich um " Grundelemente auf niedriger Ebene". Sie können es verwenden, um benutzerdefinierte Synchronisationsobjekte zu erstellen, die Verknüpfungsreihenfolge eines Thread-Baums zu steuern usw. Wenn Sie sich nicht vorstellen können, warum Sie es verwenden müssen, verwenden Sie es nicht und bleiben Sie dabei threading.
Abarnert

Antworten:

260

Was Giulio Franco sagt, gilt für Multithreading vs. Multiprocessing im Allgemeinen .

Python * hat jedoch ein zusätzliches Problem: Es gibt eine globale Interpretersperre, die verhindert, dass zwei Threads im selben Prozess gleichzeitig Python-Code ausführen. Dies bedeutet, dass wenn Sie 8 Kerne haben und Ihren Code so ändern, dass 8 Threads verwendet werden, 800% der CPU nicht verwendet werden können und 8x schneller ausgeführt werden kann. Es wird dieselbe 100% ige CPU verwenden und mit derselben Geschwindigkeit ausgeführt. (In Wirklichkeit läuft es etwas langsamer, da das Threading zusätzlichen Aufwand verursacht, selbst wenn Sie keine gemeinsam genutzten Daten haben, aber ignorieren Sie dies vorerst.)

Hiervon gibt es Ausnahmen. Wenn die umfangreiche Berechnung Ihres Codes nicht in Python erfolgt, sondern in einer Bibliothek mit benutzerdefiniertem C-Code, die eine ordnungsgemäße GIL-Behandlung ausführt, wie z. B. einer Numpy-App, erhalten Sie den erwarteten Leistungsvorteil durch Threading. Das Gleiche gilt, wenn die umfangreiche Berechnung von einem Unterprozess durchgeführt wird, den Sie ausführen und auf den Sie warten.

Noch wichtiger ist, dass es Fälle gibt, in denen dies keine Rolle spielt. Beispielsweise verbringt ein Netzwerkserver die meiste Zeit damit, Pakete aus dem Netzwerk zu lesen, und eine GUI-App verbringt die meiste Zeit damit, auf Benutzerereignisse zu warten. Ein Grund für die Verwendung von Threads in einem Netzwerkserver oder einer GUI-App besteht darin, dass Sie lang laufende "Hintergrundaufgaben" ausführen können, ohne den Hauptthread daran zu hindern, weiterhin Netzwerkpakete oder GUI-Ereignisse zu bedienen. Und das funktioniert gut mit Python-Threads. (In technischer Hinsicht bedeutet dies, dass Python-Threads Ihnen Parallelität bieten, obwohl sie Ihnen keine Kernparallelität bieten.)

Wenn Sie jedoch ein CPU-gebundenes Programm in reinem Python schreiben, ist die Verwendung weiterer Threads im Allgemeinen nicht hilfreich.

Die Verwendung separater Prozesse hat keine derartigen Probleme mit der GIL, da jeder Prozess seine eigene separate GIL hat. Natürlich haben Sie immer noch dieselben Kompromisse zwischen Threads und Prozessen wie in allen anderen Sprachen - es ist schwieriger und teurer, Daten zwischen Prozessen auszutauschen als zwischen Threads. Es kann kostspielig sein, eine große Anzahl von Prozessen auszuführen oder zu erstellen und zu zerstören sie häufig usw. Aber die GIL belastet das Gleichgewicht in Bezug auf Prozesse auf eine Weise, die beispielsweise für C oder Java nicht zutrifft. Daher werden Sie in Python viel häufiger Multiprocessing verwenden als in C oder Java.


In der Zwischenzeit bringt Pythons Philosophie "Batterien enthalten" einige gute Neuigkeiten: Es ist sehr einfach, Code zu schreiben, der mit einem einzeiligen Wechsel zwischen Threads und Prozessen hin- und hergeschaltet werden kann.

Wenn Sie Ihren Code in eigenständigen "Jobs" entwerfen, die nichts mit anderen Jobs (oder dem Hauptprogramm) außer Eingabe und Ausgabe teilen, können Sie die concurrent.futuresBibliothek verwenden, um Ihren Code um einen Thread-Pool wie folgt zu schreiben:

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.submit(job, argument)
    executor.map(some_function, collection_of_independent_things)
    # ...

Sie können sogar die Ergebnisse dieser Jobs abrufen und an weitere Jobs weitergeben, auf Dinge in der Reihenfolge ihrer Ausführung oder in der Reihenfolge ihrer Fertigstellung warten usw.; Lesen Sie den Abschnitt über FutureObjekte für Details.

Wenn sich herausstellt, dass Ihr Programm ständig 100% CPU verwendet und das Hinzufügen weiterer Threads nur langsamer wird, tritt das GIL-Problem auf, sodass Sie zu Prozessen wechseln müssen. Alles was Sie tun müssen, ist diese erste Zeile zu ändern:

with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:

Die einzige wirkliche Einschränkung besteht darin, dass die Argumente und Rückgabewerte Ihrer Jobs abwählbar sein müssen (und nicht zu viel Zeit oder Speicher benötigen, um abgehackt zu werden), um prozessübergreifend verwendet werden zu können. Normalerweise ist dies kein Problem, aber manchmal ist es das auch.


Aber was ist, wenn Ihre Jobs nicht in sich geschlossen sein können? Wenn Sie Ihren Code in Form von Jobs entwerfen können, die Nachrichten von einem zum anderen weitergeben, ist dies immer noch recht einfach. Möglicherweise müssen Sie Pools verwenden threading.Threadoder sich multiprocessing.Processnicht darauf verlassen. Und Sie müssen queue.Queueoder multiprocessing.QueueObjekte explizit erstellen . (Es gibt viele andere Optionen - Pipes, Sockets, Dateien mit Herden ... aber der Punkt ist, dass Sie etwas manuell tun müssen, wenn die automatische Magie eines Executors nicht ausreicht.)

Aber was ist, wenn Sie sich nicht einmal auf die Weitergabe von Nachrichten verlassen können? Was ist, wenn Sie zwei Jobs benötigen, um dieselbe Struktur zu mutieren und die Änderungen der anderen zu sehen? In diesem Fall müssen Sie eine manuelle Synchronisierung (Sperren, Semaphoren, Bedingungen usw.) durchführen und, wenn Sie Prozesse verwenden möchten, explizite Shared-Memory-Objekte zum Booten. Dies ist der Fall, wenn Multithreading (oder Multiprocessing) schwierig wird. Wenn Sie es vermeiden können, großartig; Wenn Sie nicht können, müssen Sie mehr lesen, als jemand in eine SO-Antwort eingeben kann.


In einem Kommentar wollten Sie wissen, was sich zwischen Threads und Prozessen in Python unterscheidet. Wirklich, wenn Sie die Antwort von Giulio Franco und meine und alle unsere Links lesen, sollte das alles abdecken… aber eine Zusammenfassung wäre definitiv nützlich, also hier ist:

  1. Threads teilen standardmäßig Daten. Prozesse nicht.
  2. Infolge von (1) erfordert das Senden von Daten zwischen Prozessen im Allgemeinen das Beizen und Entfernen von Daten. ** **.
  3. Als weitere Konsequenz von (1) erfordert der direkte Austausch von Daten zwischen Prozessen im Allgemeinen, dass diese in Formate auf niedriger Ebene wie Wert, Array und ctypesTypen gebracht werden.
  4. Prozesse unterliegen nicht der GIL.
  5. Auf einigen Plattformen (hauptsächlich Windows) ist das Erstellen und Zerstören von Prozessen viel teurer.
  6. Es gibt einige zusätzliche Einschränkungen für Prozesse, von denen einige auf verschiedenen Plattformen unterschiedlich sind. Einzelheiten finden Sie in den Programmierrichtlinien .
  7. Das threadingModul verfügt nicht über einige Funktionen des multiprocessingModuls. (Sie können verwenden multiprocessing.dummy, um den größten Teil der fehlenden API über Threads zu erhalten, oder Sie können übergeordnete Module verwenden, concurrent.futuresohne sich darum zu kümmern.)

* Es ist nicht Python, die Sprache, die dieses Problem hat, sondern CPython, die "Standard" -Implementierung dieser Sprache. Einige andere Implementierungen haben keine GIL, wie Jython.

** Wenn Sie die Fork- Start-Methode für die Mehrfachverarbeitung verwenden, die Sie auf den meisten Nicht-Windows-Plattformen verwenden können, erhält jeder untergeordnete Prozess alle Ressourcen, über die die Eltern beim Starten des Kindes verfügten. Dies kann eine weitere Möglichkeit sein, Daten an Kinder zu übergeben.

abarnert
quelle
Danke, aber ich bin nicht sicher, ob ich alles verstanden habe. Wie auch immer, ich versuche es ein bisschen zu Lernzwecken zu machen, und ein bisschen, weil ich mit einer naiven Verwendung von Thread die Geschwindigkeit meines Codes halbiert habe (mehr als 1000 Threads gleichzeitig starten, wobei jeder eine externe App aufruft. Dies ist gesättigt die CPU, aber es gibt eine x2 Erhöhung der Geschwindigkeit). Ich denke, die intelligente Verwaltung des Threads könnte die Geschwindigkeit meines Codes wirklich verbessern.
Lucacerone
3
@LucaCerone: Ah, wenn Ihr Code die meiste Zeit auf externe Programme wartet, dann wird er vom Threading profitieren. Guter Punkt. Lassen Sie mich die Antwort bearbeiten, um das zu erklären.
Abarnert
2
@ LucaCerone: In der Zwischenzeit, welche Teile verstehst du nicht? Ohne den Kenntnisstand zu kennen, mit dem Sie beginnen, ist es schwierig, eine gute Antwort zu schreiben. Mit einigen Rückmeldungen können wir uns jedoch möglicherweise etwas einfallen lassen, das Ihnen und zukünftigen Lesern hilfreich ist.
Abarnert
3
@LucaCerone Sie sollten den PEP für Multiprocessing lesen hier . Es enthält Timings und Beispiele für Threads im Vergleich zu Multiprocessing.
Mr2ert
1
@LucaCerone: Wenn das Objekt, an das die Methode gebunden ist, keinen komplexen Status hat, besteht die einfachste Problemumgehung für das Beizproblem darin, eine dumme Wrapper-Funktion zu schreiben, die das Objekt generiert und seine Methode aufruft. Wenn es nicht komplexen Zustand hat, dann müssen Sie wahrscheinlich , um es picklable zu machen (was ziemlich einfach ist, die pickleDokumente erklären), und dann im schlimmsten Fall Ihr dummer Wrapper ist def wrapper(obj, *args): return obj.wrapper(*args).
Abarnert
32

In einem Prozess können mehrere Threads vorhanden sein. Die Threads, die zu demselben Prozess gehören, teilen sich denselben Speicherbereich (können aus denselben Variablen lesen und in diese schreiben und sich gegenseitig stören). Im Gegenteil, verschiedene Prozesse leben in verschiedenen Speicherbereichen, und jeder von ihnen hat seine eigenen Variablen. Um zu kommunizieren, müssen Prozesse andere Kanäle (Dateien, Pipes oder Sockets) verwenden.

Wenn Sie eine Berechnung parallelisieren möchten, benötigen Sie wahrscheinlich Multithreading, da die Threads wahrscheinlich im selben Speicher zusammenarbeiten sollen.

In Bezug auf die Leistung lassen sich Threads schneller erstellen und verwalten als Prozesse (da das Betriebssystem keinen neuen virtuellen Speicherbereich zuweisen muss), und die Kommunikation zwischen Threads ist normalerweise schneller als die Kommunikation zwischen Prozessen. Threads sind jedoch schwieriger zu programmieren. Threads können sich gegenseitig stören und in den Speicher des anderen schreiben. Die Art und Weise, wie dies geschieht, ist jedoch nicht immer offensichtlich (aufgrund verschiedener Faktoren, hauptsächlich der Neuanordnung von Anweisungen und des Zwischenspeicherns von Speicher). Daher benötigen Sie Synchronisationsprimitive, um den Zugriff zu steuern zu Ihren Variablen.

Giulio Franco
quelle
12
Hier fehlen einige sehr wichtige Informationen über die GIL, was sie irreführend macht.
Abarnert
1
@ mr2ert: Ja, das sind die sehr wichtigen Informationen auf den Punkt gebracht. :) Aber es ist etwas komplizierter als das, weshalb ich eine separate Antwort geschrieben habe.
Abarnert
2
Ich dachte, ich hätte kommentiert und gesagt, dass @abarnert Recht hat, und ich habe die GIL vergessen, als ich hier antwortete. Diese Antwort ist also falsch, Sie sollten sie nicht positiv bewerten.
Giulio Franco
6
Ich habe diese Antwort abgelehnt, weil sie immer noch überhaupt nicht antwortet, was der Unterschied zwischen Python threadingund Python ist multiprocessing.
Antti Haapala
Ich habe gelesen, dass es für jeden Prozess eine GIL gibt. Aber verwenden alle Prozesse denselben Python-Interpreter oder gibt es einen separaten Interpreter pro Thread?
Variable
3

Ich glaube, dieser Link beantwortet Ihre Frage auf elegante Weise.

Um es kurz zu machen: Wenn eines Ihrer Unterprobleme warten muss, bis ein anderes beendet ist, ist Multithreading gut (z. B. bei schweren E / A-Vorgängen). Im Gegensatz dazu wird Multiprocessing empfohlen, wenn Ihre Unterprobleme tatsächlich gleichzeitig auftreten können. Sie werden jedoch nicht mehr Prozesse als Ihre Anzahl von Kernen erstellen.

ehfaafzv
quelle
3

Zitate zur Python-Dokumentation

Ich habe die wichtigsten Python-Dokumentationszitate zu Process vs Threads und der GIL unter folgender Adresse hervorgehoben: Was ist die globale Interpretersperre (GIL) in CPython?

Prozess gegen Thread-Experimente

Ich habe ein bisschen Benchmarking durchgeführt, um den Unterschied konkreter darzustellen.

Im Benchmark habe ich die CPU- und E / A-gebundene Arbeit für verschiedene Anzahlen von Threads auf einer 8-Hyperthread- CPU zeitgesteuert . Die pro Thread gelieferte Arbeit ist immer gleich, sodass mehr Threads mehr Gesamtarbeit bedeuten.

Die Ergebnisse waren:

Geben Sie hier die Bildbeschreibung ein

Plotdaten .

Schlussfolgerungen:

  • Bei CPU-gebundener Arbeit ist die Mehrfachverarbeitung immer schneller, vermutlich aufgrund der GIL

  • für IO-gebundene Arbeit. beide sind genau gleich schnell

  • Threads skalieren nur bis zu ungefähr 4x anstelle der erwarteten 8x, da ich auf einer 8-Hyperthread-Maschine bin.

    Vergleichen Sie dies mit einer C POSIX-CPU-gebundenen Arbeit, die die erwartete 8-fache Geschwindigkeit erreicht: Was bedeuten "real", "user" und "sys" in der Ausgabe der Zeit (1)?

    TODO: Ich kenne den Grund dafür nicht, es müssen andere Python-Ineffizienzen ins Spiel kommen.

Testcode:

#!/usr/bin/env python3

import multiprocessing
import threading
import time
import sys

def cpu_func(result, niters):
    '''
    A useless CPU bound function.
    '''
    for i in range(niters):
        result = (result * result * i + 2 * result * i * i + 3) % 10000000
    return result

class CpuThread(threading.Thread):
    def __init__(self, niters):
        super().__init__()
        self.niters = niters
        self.result = 1
    def run(self):
        self.result = cpu_func(self.result, self.niters)

class CpuProcess(multiprocessing.Process):
    def __init__(self, niters):
        super().__init__()
        self.niters = niters
        self.result = 1
    def run(self):
        self.result = cpu_func(self.result, self.niters)

class IoThread(threading.Thread):
    def __init__(self, sleep):
        super().__init__()
        self.sleep = sleep
        self.result = self.sleep
    def run(self):
        time.sleep(self.sleep)

class IoProcess(multiprocessing.Process):
    def __init__(self, sleep):
        super().__init__()
        self.sleep = sleep
        self.result = self.sleep
    def run(self):
        time.sleep(self.sleep)

if __name__ == '__main__':
    cpu_n_iters = int(sys.argv[1])
    sleep = 1
    cpu_count = multiprocessing.cpu_count()
    input_params = [
        (CpuThread, cpu_n_iters),
        (CpuProcess, cpu_n_iters),
        (IoThread, sleep),
        (IoProcess, sleep),
    ]
    header = ['nthreads']
    for thread_class, _ in input_params:
        header.append(thread_class.__name__)
    print(' '.join(header))
    for nthreads in range(1, 2 * cpu_count):
        results = [nthreads]
        for thread_class, work_size in input_params:
            start_time = time.time()
            threads = []
            for i in range(nthreads):
                thread = thread_class(work_size)
                threads.append(thread)
                thread.start()
            for i, thread in enumerate(threads):
                thread.join()
            results.append(time.time() - start_time)
        print(' '.join('{:.6e}'.format(result) for result in results))

GitHub Upstream + Plotten von Code im selben Verzeichnis .

Getestet unter Ubuntu 18.10, Python 3.6.7, in einem Lenovo ThinkPad P51-Laptop mit CPU: Intel Core i7-7820HQ-CPU (4 Kerne / 8 Threads), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB), SSD: Samsung MZVLB512HAJQ- 000L7 (3.000 MB / s).

Visualisieren Sie, welche Threads zu einem bestimmten Zeitpunkt ausgeführt werden

In diesem Beitrag unter https://rohanvarma.me/GIL/ habe ich gelernt, dass Sie einen Rückruf ausführen können, wenn ein Thread mit dem target=Argument vonthreading.Thread und dasselbe für geplant ist multiprocessing.Process.

Auf diese Weise können wir genau anzeigen, welcher Thread zu jedem Zeitpunkt ausgeführt wird. Wenn dies erledigt ist, würden wir so etwas sehen (ich habe dieses spezielle Diagramm erstellt):

            +--------------------------------------+
            + Active threads / processes           +
+-----------+--------------------------------------+
|Thread   1 |********     ************             |
|         2 |        *****            *************|
+-----------+--------------------------------------+
|Process  1 |***  ************** ******  ****      |
|         2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
            + Time -->                             +
            +--------------------------------------+

was zeigen würde, dass:

  • Threads werden von der GIL vollständig serialisiert
  • Prozesse können parallel ablaufen
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1

Hier sind einige Leistungsdaten für Python 2.6.x, die die Vorstellung in Frage stellen, dass Threading in IO-gebundenen Szenarien leistungsfähiger ist als Multiprocessing. Diese Ergebnisse stammen von einem IBM System x3650 M4 BD mit 40 Prozessoren.

E / A-gebundene Verarbeitung: Der Prozesspool hat eine bessere Leistung als der Thread-Pool

>>> do_work(50, 300, 'thread','fileio')
do_work function took 455.752 ms

>>> do_work(50, 300, 'process','fileio')
do_work function took 319.279 ms

CPU-gebundene Verarbeitung: Der Prozesspool schnitt besser ab als der Thread-Pool

>>> do_work(50, 2000, 'thread','square')
do_work function took 338.309 ms

>>> do_work(50, 2000, 'process','square')
do_work function took 287.488 ms

Dies sind keine strengen Tests, aber sie sagen mir, dass Multiprocessing im Vergleich zu Threading nicht ganz unperformant ist.

Code, der in der interaktiven Python-Konsole für die oben genannten Tests verwendet wird

from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
import time
import sys
import os
from glob import glob

text_for_test = str(range(1,100000))

def fileio(i):
 try :
  os.remove(glob('./test/test-*'))
 except : 
  pass
 f=open('./test/test-'+str(i),'a')
 f.write(text_for_test)
 f.close()
 f=open('./test/test-'+str(i),'r')
 text = f.read()
 f.close()


def square(i):
 return i*i

def timing(f):
 def wrap(*args):
  time1 = time.time()
  ret = f(*args)
  time2 = time.time()
  print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
  return ret
 return wrap

result = None

@timing
def do_work(process_count, items, process_type, method) :
 pool = None
 if process_type == 'process' :
  pool = Pool(processes=process_count)
 else :
  pool = ThreadPool(processes=process_count)
 if method == 'square' : 
  multiple_results = [pool.apply_async(square,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]
 else :
  multiple_results = [pool.apply_async(fileio,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]


do_work(50, 300, 'thread','fileio')
do_work(50, 300, 'process','fileio')

do_work(50, 2000, 'thread','square')
do_work(50, 2000, 'process','square')
Mario Aguilera
quelle
Ich habe Ihren Code verwendet (den Glob- Teil entfernt) und diese interessanten Ergebnisse mit Python 2.6.6 gefunden:>>> do_work(50, 300, 'thread', 'fileio') --> 237.557 ms >>> do_work(50, 300, 'process', 'fileio') --> 323.963 ms >>> do_work(50, 2000, 'thread', 'square') --> 232.082 ms >>> do_work(50, 2000, 'process', 'square') --> 282.785 ms
Alan Garrido
-5

Nun, der größte Teil der Frage wird von Giulio Franco beantwortet. Ich werde weiter auf das Verbraucher-Hersteller-Problem eingehen, das Sie vermutlich auf den richtigen Weg für Ihre Lösung für die Verwendung einer Multithread-App bringen wird.

fill_count = Semaphore(0) # items produced
empty_count = Semaphore(BUFFER_SIZE) # remaining space
buffer = Buffer()

def producer(fill_count, empty_count, buffer):
    while True:
        item = produceItem()
        empty_count.down();
        buffer.push(item)
        fill_count.up()

def consumer(fill_count, empty_count, buffer):
    while True:
        fill_count.down()
        item = buffer.pop()
        empty_count.up()
        consume_item(item)

Weitere Informationen zu den Synchronisationsprimitiven finden Sie unter:

 http://linux.die.net/man/7/sem_overview
 http://docs.python.org/2/library/threading.html

Der Pseudocode ist oben. Ich nehme an, Sie sollten das Produzent-Verbraucher-Problem durchsuchen, um weitere Referenzen zu erhalten.

Innosam
quelle
Entschuldigung Innosam, aber das scheint mir C ++? danke für die links :)
lucacerone
Tatsächlich sind die Ideen hinter Multiprocessing und Multithreading sprachunabhängig. Die Lösung wäre ähnlich dem obigen Code.
Innosam
2
Dies ist nicht C ++; Es ist ein Pseudocode (oder ein Code für eine meist dynamisch typisierte Sprache mit einer C-ähnlichen Syntax. Trotzdem halte ich es für nützlicher, einen Python-ähnlichen Pseudocode zu schreiben, um Python-Benutzer zu unterrichten (insbesondere, da der Python-ähnliche Pseudocode häufig verwendet wird) Es stellt sich heraus, dass es sich um ausführbaren Code handelt oder zumindest in der Nähe davon, was für C-ähnlichen Pseudocode selten der Fall ist…)
abarnert
Ich habe es als Python-ähnlichen Pseudocode umgeschrieben (auch unter Verwendung von OO und Übergabe von Parametern anstelle von globalen Objekten). Sie können jederzeit zurückkehren, wenn Sie der Meinung sind, dass dies die Dinge weniger klar macht.
Abarnert
Es ist auch erwähnenswert, dass in der Python-stdlib eine synchronisierte Warteschlange integriert ist, die all diese Details zusammenfasst, und dass die Thread- und Prozesspool-APIs die Dinge noch weiter abstrahieren. Es lohnt sich auf jeden Fall zu verstehen, wie synchronisierte Warteschlangen unter der Decke funktionieren, aber Sie müssen selten selbst eine schreiben.
Abarnert