{"id":576190,"date":"2025-06-18T06:44:40","date_gmt":"2025-06-18T05:44:40","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=576190"},"modified":"2025-06-18T06:44:49","modified_gmt":"2025-06-18T05:44:49","slug":"schnelleres-python-nebenlaeufigkeit-bei-async-await-und-threading","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/de\/pycharm\/2025\/06\/schnelleres-python-nebenlaeufigkeit-bei-async-await-und-threading\/","title":{"rendered":"Schnelleres Python: Nebenl\u00e4ufigkeit bei async\/await und Threading"},"content":{"rendered":"<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"2560\" height=\"1440\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/PC-social-BlogFeatured-1280x720-2x-17.png\" alt=\"Faster Python Concurrency in async\/await and threading\" class=\"wp-image-573955\" \/><\/figure>\n<p>Wenn Sie schon eine Weile mit Python programmieren, und insbesondere wenn Sie Frameworks und Bibliotheken wie FastAPI und discord.py nutzen, haben Sie wahrscheinlich schon einmal <code>async\/await<\/code> oder <code>asyncio<\/code> verwendet. Vielleicht haben Sie auch schon Aussagen wie \u201eEs gibt kein echtes Multithreading in Python\u201c geh\u00f6rt, und vielleicht kennen Sie auch den ber\u00fchmt-ber\u00fcchtigten GIL in Python. Angesichts des \u201eunechten\u201c Multithreadings in Python fragen Sie sich vielleicht, was der Unterschied zwischen <code>async\/await<\/code> und Multithreading in Wirklichkeit ist \u2013 insbesondere bei der Programmierung in Python. Im folgenden Artikel beantworten wir Ihnen diese Frage!<\/p>\n<h2 class=\"wp-block-heading\" id=\"what-is-multithreading\">Was ist Multithreading?<\/h2>\n<p>In der Programmierung bezieht sich Multithreading auf die F\u00e4higkeit eines Programms, mehrere sequenzielle Aufgaben (sogenannte Threads) gleichzeitig auszuf\u00fchren. Diese Threads k\u00f6nnen auf einem einzelnen Prozessorkern oder auf mehreren Kernen ausgef\u00fchrt werden. Aufgrund der Beschr\u00e4nkung durch den Global Interpreter Lock (GIL) wird bei Multithreading in Python jedoch nur ein einziger Kern verwendet. Die Ausnahme ist die NoGIL-Version von Python (auch \u201ethread-free\u201c genannt, also \u201emit freiem Threading\u201c), die den GIL abschafft und auf die wir in Teil 2 dieser Reihe eingehen werden. In diesem Blogartikel gehen wir davon aus, dass der GIL stets vorhanden ist.<\/p>\n<h2 class=\"wp-block-heading\" id=\"what-is-concurrency\">Was ist Nebenl\u00e4ufigkeit?<\/h2>\n<p>In der Programmierung bedeutet Nebenl\u00e4ufigkeit, dass der Computer mehr als eine Aufgabe gleichzeitig abarbeitet \u2013 oder zumindest diesen Anschein erweckt, auch wenn die unterschiedlichen Aufgaben auf einem einzigen Prozessor ausgef\u00fchrt werden. Durch die Verwaltung von Ressourcen und Interaktionen zwischen unterschiedlichen Teilen eines Programms k\u00f6nnen verschiedene Aufgaben unabh\u00e4ngig voneinander und in sich \u00fcberschneidenden Zeitintervallen voranschreiten.<\/p>\n<h3 class=\"wp-block-heading\" id=\"both-asyncio-and-threading-appear-concurrent-in-python\">In Python sind sowohl <code>asyncio<\/code> als auch <code>threading<\/code> scheinbar nebenl\u00e4ufig<\/h3>\n<p>Fl\u00fcchtig betrachtet vermitteln die Python-Bibliotheken <code>asyncio<\/code> und <code>threading<\/code> beide den Eindruck von Nebenl\u00e4ufigkeit. In Wirklichkeit f\u00fchren Ihre CPUs jedoch nicht mehrere Aufgaben zur genau gleichen Zeit aus. Sie erwecken nur diesen Eindruck.<\/p>\n<p>Stellen Sie sich vor, Sie laden G\u00e4ste zu einem mehrg\u00e4ngigen Abendessen ein. Die Zubereitung einiger dieser G\u00e4nge ist zeitaufw\u00e4ndig \u2013 der Kuchen braucht Zeit, um im Ofen braun zu werden, und die Suppe muss l\u00e4nger auf dem Herd k\u00f6cheln. W\u00e4hrend wir darauf warten, dass diese Gerichte gar sind, stehen wir nicht nur herum und warten. In der Zwischenzeit tun wir etwas anderes. \u00c4hnlich ist es auch mit der Nebenl\u00e4ufigkeit in Python. Manchmal muss Ihr Python-Prozess darauf warten, dass etwas erledigt wird. Zum Beispiel werden einige Ein-\/Ausgabeprozesse (E\/A) vom Betriebssystem abgewickelt, und diese Zeit muss der Python-Prozess einfach abwarten. W\u00e4hrend dieser Wartezeit k\u00f6nnen wir mit async einen anderen Python-Prozess ausf\u00fchren.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"896\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-23.png\" alt=\"Python-Multithreading und asyncio\" class=\"wp-image-574536\" \/><\/figure>\n<h3 class=\"wp-block-heading\" id=\"the-difference-is-who-is-in-charge\">Der Unterschied liegt darin, wer das Sagen hat<\/h3>\n<p>Wenn sowohl <code>asyncio<\/code> als auch <code>threading<\/code> nebenl\u00e4ufig erscheinen, was ist dann der eigentliche Unterschied zwischen ihnen? Nun, der Hauptunterschied besteht darin, wer entscheidet, welcher Prozess wann ausgef\u00fchrt wird. Der Ansatz von <code>async\/await<\/code> wird manchmal auch als kooperative Nebenl\u00e4ufigkeit bezeichnet. Eine Coroutine oder ein Future gibt die Kontrolle an eine andere Coroutine oder ein anderes Future ab, damit auch andere Programmteile zum Zug kommen. Bei <code>Threading<\/code> hingegen hat der betriebssystemeigene Manager die Kontrolle dar\u00fcber, welcher Prozess gerade ausgef\u00fchrt wird.<\/p>\n<p>Kooperative Nebenl\u00e4ufigkeit ist wie ein Meeting, bei dem das Mikrofon herumgereicht wird, damit die Teilnehmenden alle sprechen k\u00f6nnen. Die Person, die das Mikrofon hat, kann sprechen, und wenn sie fertig ist, gibt sie das Mikrofon an die n\u00e4chste Person weiter. Im Gegensatz dazu entspricht Multithreading einem Meeting, bei dem jemand den Vorsitz hat, und diese Person bestimmt, wer zu einem bestimmten Zeitpunkt das Wort hat.<\/p>\n<h2 class=\"wp-block-heading\" id=\"writing-concurrent-code-in-python\">Nebenl\u00e4ufige Programmierung in Python<\/h2>\n<p>Sehen wir uns an, wie Nebenl\u00e4ufigkeit in Python funktioniert, indem wir einige beispielhafte Codeabschnitte schreiben. Wir werden ein Fastfood-Restaurant simulieren und dabei sowohl <code>asyncio<\/code> als auch <code>threading<\/code> einsetzen.<\/p>\n<h3 class=\"wp-block-heading\" id=\"how-async-await-works-in-python\">So funktioniert <code>async\/await<\/code> in Python<\/h3>\n<p>Das Paket <code>asyncio<\/code> wurde in Python 3.4 und die Schl\u00fcsselw\u00f6rter <code>async<\/code> und <code>await<\/code> in Python 3.5 eingef\u00fchrt. Einer der wichtigsten Aspekte, die <code>async\/await<\/code> m\u00f6glich machen, ist die Verwendung von Coroutinen. Coroutinen in Python sind in Wirklichkeit Generatoren, die so umfunktioniert wurden, dass sie anhalten und die Kontrolle an die Hauptfunktion zur\u00fcckgeben k\u00f6nnen.<\/p>\n<p>Nehmen wir jetzt an, in einem Burger-Restaurant arbeitet gerade nur eine Person. Die Auftr\u00e4ge werden in der Reihenfolge ihres Eingangs abgearbeitet, und es sind keine asynchronen Operationen m\u00f6glich:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import time\n\n\ndef make_burger(order_num):\n    print(f\"Preparing burger #{order_num}...\")\n    time.sleep(5) # time for making the burger\n    print(f\"Burger made #{order_num}\")\n\n\ndef main():\n    for i in range(3):\n        make_burger(i)\n\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    main()\n    elapsed = time.perf_counter() - s\n    print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>Dies wird eine Weile dauern:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Preparing burger #0...\n\nBurger made #0\n\nPreparing burger #1...\n\nBurger made #1\n\nPreparing burger #2...\n\nBurger made #2\n\nOrders completed in 15.01 seconds.<\/pre>\n<p>Nehmen wir jetzt an, das Restaurant stellt mehr Personal ein, sodass die Arbeit parallel erledigt werden kann:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport time\n\nasync def make_burger(order_num):\n\n\u00a0\u00a0\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0\u00a0await asyncio.sleep(5) # time for making the burger\n\n\u00a0\u00a0\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\nasync def main():\n\n\u00a0\u00a0\u00a0\u00a0order_queue = []\n\n\u00a0\u00a0\u00a0\u00a0for i in range(3):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_queue.append(make_burger(i))\n\n\u00a0\u00a0\u00a0\u00a0await asyncio.gather(*(order_queue))\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0\u00a0asyncio.run(main())\n\n\u00a0\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>Der Unterschied zwischen den beiden Szenarien ist deutlich:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Preparing burger #0...\n\nPreparing burger #1...\n\nPreparing burger #2...\n\nBurger made #0\n\nBurger made #1\n\nBurger made #2\n\nOrders completed in 5.00 seconds.<\/pre>\n<p>Mit den von <code>asyncio<\/code> bereitgestellten Funktionen wie <code>run<\/code> und <code>gather<\/code> sowie den Schl\u00fcsselw\u00f6rtern <code>async<\/code> und <code>await<\/code> haben wir Coroutinen erstellt, die mehrere Burger parallel zubereiten k\u00f6nnen.<\/p>\n<p>Wir gehen nun einen Schritt weiter und erstellen eine kompliziertere Simulation. Stellen Sie sich vor, wir haben nur zwei Arbeitskr\u00e4fte und k\u00f6nnen nur zwei Burger auf einmal zubereiten.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport time\n\norder_queue = asyncio.Queue()\n\ndef take_order():\n\n\u00a0\u00a0for i in range(3):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_queue.put_nowait(make_burger(i))\n\nasync def make_burger(order_num):\n\n\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0await asyncio.sleep(5)\u00a0 # time for making the burger\n\n\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\nclass Staff:\n\n\u00a0\u00a0def __init__(self, name):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0self.name = name\n\n\u00a0\u00a0async def working(self):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0while order_queue.qsize() &gt; 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{self.name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = await order_queue.get()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{self.name} finished a task...\")\n\nasync def main():\n\n\u00a0\u00a0staff1 = Staff(name=\"John\")\n\n\u00a0\u00a0staff2 = Staff(name=\"Jane\")\n\n\u00a0\u00a0take_order()\n\n\u00a0\u00a0await asyncio.gather(staff1.working(), staff2.working())\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0asyncio.run(main())\n\n\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>Wir speichern die Aufgaben in einer Warteschlange, und die Mitarbeitenden holen sie ab.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">John is working...\n\nPreparing burger #0...\n\nJane is working...\n\nPreparing burger #1...\n\nBurger made #0\n\nJohn finished a task...\n\nJohn is working...\n\nPreparing burger #2...\n\nBurger made #1\n\nJane finished a task...\n\nBurger made #2\n\nJohn finished a task...\n\nOrders completed in 10.00 seconds.<\/pre>\n<p>In diesem Beispiel verwenden wir <code>asyncio.Queue<\/code> zum Speichern der Aufgaben \u2013 das ist insbesondere bei mehreren Aufgabenarten n\u00fctzlich, wie wir sie im n\u00e4chsten Beispiel verwenden werden.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport time\n\ntask_queue = asyncio.Queue()\n\norder_num = 0\n\nasync def take_order():\n\n\u00a0\u00a0\u00a0global order_num\n\n\u00a0\u00a0\u00a0order_num += 1\n\n\u00a0\u00a0\u00a0print(f\"Order burger and fries for order #{order_num:04d}:\")\n\n\u00a0\u00a0\u00a0burger_num = input(\"Number of burgers:\")\n\n\u00a0\u00a0\u00a0for i in range(int(burger_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(make_burger(f\"{order_num:04d}-burger{i:02d}\"))\n\n\u00a0\u00a0\u00a0fries_num = input(\"Number of fries:\")\n\n\u00a0\u00a0\u00a0for i in range(int(fries_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(make_fries(f\"{order_num:04d}-fries{i:02d}\"))\n\n\u00a0\u00a0\u00a0print(f\"Order #{order_num:04d} queued.\")\n\n\u00a0\u00a0\u00a0await task_queue.put(take_order())\n\nasync def make_burger(order_num):\n\n\u00a0\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0await asyncio.sleep(5)\u00a0 # time for making the burger\n\n\u00a0\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\nasync def make_fries(order_num):\n\n\u00a0\u00a0\u00a0print(f\"Preparing fries #{order_num}...\")\n\n\u00a0\u00a0\u00a0await asyncio.sleep(2)\u00a0 # time for making fries\n\n\u00a0\u00a0\u00a0print(f\"Fries made #{order_num}\")\n\nclass Staff:\n\n\u00a0\u00a0\u00a0def __init__(self, name):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0self.name = name\n\n\u00a0\u00a0\u00a0async def working(self):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0while True:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if task_queue.qsize() &gt; 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{self.name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = await task_queue.get()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{self.name} finish task...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await asyncio.sleep(1) #rest\n\nasync def main():\n\n\u00a0\u00a0\u00a0task_queue.put_nowait(take_order())\n\n\u00a0\u00a0\u00a0staff1 = Staff(name=\"John\")\n\n\u00a0\u00a0\u00a0staff2 = Staff(name=\"Jane\")\n\n\u00a0\u00a0\u00a0await asyncio.gather(staff1.working(), staff2.working())\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0asyncio.run(main())\n\n\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>In diesem Beispiel gibt es mehrere Aufgaben, darunter die Zubereitung von Pommes frites, die weniger Zeit in Anspruch nimmt, und das Aufnehmen von Bestellungen, also das Einholen von Benutzereingaben.<\/p>\n<p>Beachten Sie, dass das Programm beim Warten auf Benutzereingaben anh\u00e4lt, und sogar die anderen Mitarbeitenden, die nicht mit dem Aufnehmen der Bestellung besch\u00e4ftigt sind, stellen ihre Arbeit im Hintergrund ein. Das liegt daran, dass die Funktion <code>input<\/code> nicht asynchron ist und daher nicht await verwendet. Denken Sie daran: Die Kontrolle in asynchronem Code wird nur abgegeben, wenn await verwendet wird. Um dies zu beheben, ersetzen wir<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">input(\"Number of burgers:\")<\/pre>\n<p>durch<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">await asyncio.to_thread(input, \"Number of burgers:\")<\/pre>\n<p>Das Gleiche tun wir auch bei der Pommes-Zubereitung \u2013 siehe den Code unten. Beachten Sie, dass das Programm jetzt in einer Endlosschleife l\u00e4uft. Wenn wir es beenden wollen, k\u00f6nnen wir das Programm mit einer ung\u00fcltigen Eingabe absichtlich zum Absturz bringen.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport time\n\ntask_queue = asyncio.Queue()\n\norder_num = 0\n\nasync def take_order():\n\n\u00a0\u00a0\u00a0global order_num\n\n\u00a0\u00a0\u00a0order_num += 1\n\n\u00a0\u00a0\u00a0print(f\"Order burger and fries for order #{order_num:04d}:\")\n\n\u00a0\u00a0\u00a0burger_num = await asyncio.to_thread(input, \"Number of burgers:\")\n\n\u00a0\u00a0\u00a0for i in range(int(burger_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(make_burger(f\"{order_num:04d}-burger{i:02d}\"))\n\n\u00a0\u00a0\u00a0fries_num = await asyncio.to_thread(input, \"Number of fries:\")\n\n\u00a0\u00a0\u00a0for i in range(int(fries_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(make_fries(f\"{order_num:04d}-fries{i:02d}\"))\n\n\u00a0\u00a0\u00a0print(f\"Order #{order_num:04d} queued.\")\n\n\u00a0\u00a0\u00a0await task_queue.put(take_order())\n\nasync def make_burger(order_num):\n\n\u00a0\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0await asyncio.sleep(5)\u00a0 # time for making the burger\n\n\u00a0\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\nasync def make_fries(order_num):\n\n\u00a0\u00a0\u00a0print(f\"Preparing fries #{order_num}...\")\n\n\u00a0\u00a0\u00a0await asyncio.sleep(2)\u00a0 # time for making fries\n\n\u00a0\u00a0\u00a0print(f\"Fries made #{order_num}\")\n\nclass Staff:\n\n\u00a0\u00a0\u00a0def __init__(self, name):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0self.name = name\n\n\u00a0\u00a0\u00a0async def working(self):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0while True:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if task_queue.qsize() &gt; 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{self.name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = await task_queue.get()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{self.name} finish task...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await asyncio.sleep(1) #rest\n\nasync def main():\n\n\u00a0\u00a0\u00a0task_queue.put_nowait(take_order())\n\n\u00a0\u00a0\u00a0staff1 = Staff(name=\"John\")\n\n\u00a0\u00a0\u00a0staff2 = Staff(name=\"Jane\")\n\n\u00a0\u00a0\u00a0await asyncio.gather(staff1.working(), staff2.working())\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0asyncio.run(main())\n\n\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>Durch die Verwendung von <code>asyncio.to_thread<\/code> haben wir die <code>input<\/code>-Funktion in einen separaten Thread ausgelagert (<a href=\"https:\/\/docs.python.org\/3\/library\/asyncio-task.html#running-in-threads\" target=\"_blank\" rel=\"noopener\">siehe diese Referenz<\/a>). Beachten Sie jedoch: Wenn der Python-GIL vorhanden ist, funktioniert dieser Trick nur mit E\/A-gebundenen Aufgaben.<\/p>\n<p>Beim Ausf\u00fchren des obigen Codes d\u00fcrfte Ihnen au\u00dferdem auffallen, dass die Standard-Eingabe\/Ausgabe im Terminal durcheinandergew\u00fcrfelt ist. Die Benutzer-E\/A sollte von der Protokollierung der Vorg\u00e4nge getrennt werden. Wir k\u00f6nnen die Vorg\u00e4nge in einem Protokoll speichern, um sie sp\u00e4ter zu untersuchen.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport logging\n\nimport time\n\nlogger = logging.getLogger(__name__)\n\nlogging.basicConfig(filename='pyburger.log', level=logging.INFO)\n\ntask_queue = asyncio.Queue()\n\norder_num = 0\n\nclosing = False\n\nasync def take_order():\n\n\u00a0\u00a0\u00a0global order_num, closing\n\n\u00a0\u00a0\u00a0try:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_num += 1\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Taking Order #{order_num:04d}...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"Order burger and fries for order #{order_num:04d}:\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0burger_num = await asyncio.to_thread(input, \"Number of burgers:\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0for i in range(int(burger_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(make_burger(f\"{order_num:04d}-burger{i:02d}\"))\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fries_num = await asyncio.to_thread(input, \"Number of fries:\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0for i in range(int(fries_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(make_fries(f\"{order_num:04d}-fries{i:02d}\"))\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Order #{order_num:04d} queued.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"Order #{order_num:04d} queued, please wait.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task_queue.put(take_order())\n\n\u00a0\u00a0\u00a0except ValueError:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(\"Goodbye!\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(\"Closing down... stop taking orders and finish all tasks.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0closing = True\n\nasync def make_burger(order_num):\n\n\u00a0\u00a0\u00a0logger.info(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0await asyncio.sleep(5)\u00a0 # time for making the burger\n\n\u00a0\u00a0\u00a0logger.info(f\"Burger made #{order_num}\")\n\nasync def make_fries(order_num):\n\n\u00a0\u00a0\u00a0logger.info(f\"Preparing fries #{order_num}...\")\n\n\u00a0\u00a0\u00a0await asyncio.sleep(2)\u00a0 # time for making fries\n\n\u00a0\u00a0\u00a0logger.info(f\"Fries made #{order_num}\")\n\nclass Staff:\n\n\u00a0\u00a0\u00a0def __init__(self, name):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0self.name = name\n\n\u00a0\u00a0\u00a0async def working(self):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0while True:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if task_queue.qsize() &gt; 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"{self.name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = await task_queue.get()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await task\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task_queue.task_done()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"{self.name} finish task.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0elif closing:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0await asyncio.sleep(1) #rest\n\nasync def main():\n\n\u00a0\u00a0\u00a0global task_queue\n\n\u00a0\u00a0\u00a0task_queue.put_nowait(take_order())\n\n\u00a0\u00a0\u00a0staff1 = Staff(name=\"John\")\n\n\u00a0\u00a0\u00a0staff2 = Staff(name=\"Jane\")\n\n\u00a0\u00a0\u00a0print(\"Welcome to Pyburger!\")\n\n\u00a0\u00a0\u00a0logger.info(\"Ready for business!\")\n\n\u00a0\u00a0\u00a0await asyncio.gather(staff1.working(), staff2.working())\n\n\u00a0\u00a0\u00a0logger.info(\"All tasks finished. Closing now.\")\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0asyncio.run(main())\n\n\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0logger.info(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>In diesem letzten Codeblock haben wir die Infoausgaben zur Simulation in <code>pyburger.log<\/code> protokolliert und das Terminal f\u00fcr Mitteilungen an die Kundschaft reserviert. Au\u00dferdem fangen wir ung\u00fcltige Eingaben w\u00e4hrend des Bestellvorgangs ab und setzen dann das <code>closing<\/code>-Flag auf <code>True<\/code>, in der Annahme, dass hierdurch das Programm beendet werden soll. Sobald das <code>closing<\/code>-Flag auf <code>True<\/code> gesetzt ist, kehrt der Worker mit <code>return<\/code> zur\u00fcck, und damit wird auch die <code>while<\/code>-Endlosschleife der Coroutine beendet.<\/p>\n<h3 class=\"wp-block-heading\" id=\"how-does-threading-work-in-python\">Wie funktioniert <code>threading<\/code> in Python?<\/h3>\n<p>Im obigen Beispiel haben wir eine E\/A-gebundene Aufgabe in einen anderen Thread ausgelagert. Vielleicht fragen Sie sich, ob wir alle Aufgaben in separate Threads packen k\u00f6nnen, um sie parallel auszuf\u00fchren. Versuchen wir, <code>asyncio<\/code> durch <code>threading<\/code> zu ersetzen.<\/p>\n<p>Sehen Sie sich den unten gezeigten Code an, in dem wir Burger parallel zubereiten, ohne jegliche Einschr\u00e4nkungen vorzugeben:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport time\n\nasync def make_burger(order_num):\n\n\u00a0\u00a0\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0\u00a0await asyncio.sleep(5) # time for making the burger\n\n\u00a0\u00a0\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\nasync def main():\n\n\u00a0\u00a0\u00a0\u00a0order_queue = []\n\n\u00a0\u00a0\u00a0\u00a0for i in range(3):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_queue.append(make_burger(i))\n\n\u00a0\u00a0\u00a0\u00a0await asyncio.gather(*(order_queue))\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0\u00a0asyncio.run(main())\n\n\u00a0\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n\n```\n\nInstead of creating async coroutines to make the burgers, we can just send functions down different threads like this:\n\n```\n\nimport threading\n\nimport time\n\ndef make_burger(order_num):\n\n\u00a0\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0time.sleep(5) # time for making the burger\n\n\u00a0\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\ndef main():\n\n\u00a0\u00a0\u00a0order_queue = []\n\n\u00a0\u00a0\u00a0for i in range(3):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = threading.Thread(target=make_burger, args=(i,))\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_queue.append(task)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task.start()\n\n\u00a0\u00a0\u00a0for task in order_queue:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task.join()\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0main()\n\n\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>In der ersten <code>for<\/code>-Schleife in <code>main<\/code> werden Aufgaben in unterschiedlichen Threads erstellt und gestartet. Die zweite <code>for<\/code>-Schleife sorgt daf\u00fcr, dass alle Burger fertig sind, bevor das Programm weitergeht (d.\u00a0h. bevor es zu <code>main<\/code> zur\u00fcckkehrt).<\/p>\n<p>Wenn wir nur zwei Mitarbeitende haben, ist es komplizierter. Jede Arbeitskraft wird durch einen Thread repr\u00e4sentiert, und sie entnehmen ihre Aufgaben aus einer normalen Liste, in der alle Aufgaben gespeichert sind.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import threading\n\nimport time\n\norder_queue = []\n\ndef take_order():\n\n\u00a0\u00a0\u00a0for i in range(3):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_queue.append(make_burger(i))\n\ndef make_burger(order_num):\n\n\u00a0\u00a0\u00a0def making_burger():\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0time.sleep(5)\u00a0 # time for making the burger\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"Burger made #{order_num}\")\n\n\u00a0\u00a0\u00a0return making_burger\n\ndef working():\n\n\u00a0\u00a0\u00a0\u00a0\u00a0while len(order_queue) &gt; 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{threading.current_thread().name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = order_queue.pop(0)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{threading.current_thread().name} finish task...\")\n\ndef main():\n\n\u00a0\u00a0\u00a0take_order()\n\n\u00a0\u00a0\u00a0staff1 = threading.Thread(target=working, name=\"John\")\n\n\u00a0\u00a0\u00a0staff1.start()\n\n\u00a0\u00a0\u00a0staff2 = threading.Thread(target=working, name=\"Jane\")\n\n\u00a0\u00a0\u00a0staff2.start()\n\n\u00a0\u00a0\u00a0staff1.join()\n\n\u00a0\u00a0\u00a0staff2.join()\n\nif __name__ == \"__main__\":\n\n\u00a0s = time.perf_counter()\n\n\u00a0main()\n\n\u00a0elapsed = time.perf_counter() - s\n\n\u00a0print(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>Wenn Sie den obigen Code ausf\u00fchren, kann in einem der Threads ein Fehler auftreten, wenn versucht wird, eine Aufgabe aus einer leeren Liste abzuholen. Vielleicht fragen Sie sich, warum dies passiert, wenn wir doch eine Bedingung in der <code>while<\/code>-Schleife haben, die bewirken soll, dass sie nur fortgesetzt wird, wenn <code>task_queue<\/code> nicht leer ist. Trotzdem ist es zum Fehler gekommen, weil eine \u201eWettlaufsituation\u201c aufgetreten ist \u2013 eine sogenannte Race Condition.<\/p>\n<h3 class=\"wp-block-heading\" id=\"race-conditions\">Race Conditions<\/h3>\n<p>Race Conditions k\u00f6nnen auftreten, wenn mehrere Threads gleichzeitig versuchen, auf dieselbe Ressource oder dieselben Daten zuzugreifen, und es dadurch zu Problemen im System kommt. Das Timing und die Reihenfolge der Zugriffe auf eine Ressource sind wichtig f\u00fcr die Programmlogik. Ein unvorhergesehener zeitlicher Ablauf oder der parallele Zugriff mehrerer Threads zum \u00c4ndern gemeinsam genutzter Daten kann Fehler verursachen.<\/p>\n<p>Um Race Conditions in unserem Programm zu vermeiden, versehen wir <code>task_queue<\/code> mit einer Sperre:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">queue_lock = threading.Lock()<\/pre>\n<p>F\u00fcr unsere Arbeit m\u00fcssen wir sicherstellen, dass wir Zugriffsrechte auf die Warteschlange haben, wenn wir ihre L\u00e4nge \u00fcberpr\u00fcfen und Aufgaben aus ihr abrufen. Solange wir diese Rechte haben, k\u00f6nnen andere Threads nicht auf die Warteschlange zugreifen:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def working():\n\n\u00a0\u00a0\u00a0while True:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0with queue_lock:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if len(order_queue) == 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = order_queue.pop(0)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{threading.current_thread().name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"{threading.current_thread().name} finish task...\")\n\n```\n\nBased on what we have learned so far, we can complete our final code with threading like this:\n\n```\n\nimport logging\n\nimport threading\n\nimport time\n\nlogger = logging.getLogger(__name__)\n\nlogging.basicConfig(filename=\"pyburger_threads.log\", level=logging.INFO)\n\nqueue_lock = threading.Lock()\n\ntask_queue = []\n\norder_num = 0\n\nclosing = False\n\ndef take_order():\n\n\u00a0\u00a0\u00a0global order_num, closing\n\n\u00a0\u00a0\u00a0try:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order_num += 1\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Taking Order #{order_num:04d}...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"Order burger and fries for order #{order_num:04d}:\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0burger_num = input(\"Number of burgers:\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0for i in range(int(burger_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0with queue_lock:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task_queue.append(make_burger(f\"{order_num:04d}-burger{i:02d}\"))\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fries_num = input(\"Number of fries:\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0for i in range(int(fries_num)):\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0with queue_lock:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task_queue.append(make_fries(f\"{order_num:04d}-fries{i:02d}\"))\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Order #{order_num:04d} queued.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(f\"Order #{order_num:04d} queued, please wait.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0with queue_lock:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task_queue.append(take_order)\n\n\u00a0\u00a0\u00a0except ValueError:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(\"Goodbye!\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(\"Closing down... stop taking orders and finish all tasks.\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0closing = True\n\ndef make_burger(order_num):\n\n\u00a0\u00a0\u00a0def making_burger():\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Preparing burger #{order_num}...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0time.sleep(5)\u00a0 # time for making the burger\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Burger made #{order_num}\")\n\n\u00a0\u00a0\u00a0return making_burger\n\ndef make_fries(order_num):\n\n\u00a0\u00a0\u00a0def making_fries():\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Preparing fried #{order_num}...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0time.sleep(2)\u00a0 # time for making fries\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"Fries made #{order_num}\")\n\n\u00a0\u00a0\u00a0return making_fries\n\ndef working():\n\n\u00a0\u00a0\u00a0while True:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0with queue_lock:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if len(task_queue) == 0:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if closing:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = None\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task = task_queue.pop(0)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if task:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"{threading.current_thread().name} is working...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0task()\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0logger.info(f\"{threading.current_thread().name} finish task...\")\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else:\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0time.sleep(1)\u00a0 # rest\n\ndef main():\n\n\u00a0\u00a0\u00a0print(\"Welcome to Pyburger!\")\n\n\u00a0\u00a0\u00a0logger.info(\"Ready for business!\")\n\n\u00a0\u00a0\u00a0task_queue.append(take_order)\n\n\u00a0\u00a0\u00a0staff1 = threading.Thread(target=working, name=\"John\")\n\n\u00a0\u00a0\u00a0staff1.start()\n\n\u00a0\u00a0\u00a0staff2 = threading.Thread(target=working, name=\"Jane\")\n\n\u00a0\u00a0\u00a0staff2.start()\n\n\u00a0\u00a0\u00a0staff1.join()\n\n\u00a0\u00a0\u00a0staff2.join()\n\n\u00a0\u00a0\u00a0logger.info(\"All tasks finished. Closing now.\")\n\nif __name__ == \"__main__\":\n\n\u00a0\u00a0\u00a0s = time.perf_counter()\n\n\u00a0\u00a0\u00a0main()\n\n\u00a0\u00a0\u00a0elapsed = time.perf_counter() - s\n\n\u00a0\u00a0\u00a0logger.info(f\"Orders completed in {elapsed:0.2f} seconds.\")<\/pre>\n<p>Wenn Sie die beiden Codebl\u00f6cke mit <code>asyncio<\/code> bzw. <code>threading<\/code> vergleichen, sollten sie \u00e4hnliche Ergebnisse liefern. Vielleicht fragen Sie sich, welches der beiden Modelle besser ist und warum Sie das eine dem anderen vorziehen sollten.<\/p>\n<p>In der Praxis ist das Schreiben von <code>asyncio<\/code>-Code einfacher als Multithreading, weil wir uns nicht selbst um potenzielle Race Conditions und Deadlocks k\u00fcmmern m\u00fcssen. Die Kontrolle wird standardm\u00e4\u00dfig von Coroutinen weitergegeben, sodass keine Sperren erforderlich sind. Allerdings k\u00f6nnen Python-Threads durchaus parallel ausgef\u00fchrt werden \u2013 nur ist dies selten der Fall, wenn der GIL vorhanden ist. Wir werden darauf zur\u00fcckkommen, wenn wir im n\u00e4chsten Blogartikel auf die NoGIL-Version von Python mit freiem Threading eingehen.<\/p>\n<h2 class=\"wp-block-heading\" id=\"benefiting-from-concurrency\">Nebenl\u00e4ufigkeit sinnvoll einsetzen<\/h2>\n<p>Warum sollten wir bei der Programmierung auf Nebenl\u00e4ufigkeit setzen? Der Hauptgrund ist Geschwindigkeit. Wie wir oben gesehen haben, k\u00f6nnen Aufgaben schneller erledigt werden, wenn wir die Wartezeiten verk\u00fcrzen k\u00f6nnen. Es gibt unterschiedliche Arten des Wartens bei Rechenvorg\u00e4ngen, davon abh\u00e4ngig verwenden wir unterschiedliche Methoden, um Zeit zu sparen.<\/p>\n<h3 class=\"wp-block-heading\" id=\"i-o-bound-tasks\">E\/A-gebundene Aufgaben<\/h3>\n<p>Eine Aufgabe oder ein Programm gilt als E\/A-gebunden, wenn die Ausf\u00fchrungsgeschwindigkeit in erster Linie durch die Geschwindigkeit von Eingabe-\/Ausgabe-Vorg\u00e4nge begrenzt wird \u2013 etwa das Einlesen von Daten aus einer Datei oder einem Netzlaufwerk oder das Warten auf Benutzereingaben. E\/A-Vorg\u00e4nge sind meist langsamer als andere CPU-Operationen, und daher k\u00f6nnen Aufgaben, die zahlreiche solche Vorg\u00e4nge enthalten, mit einem deutlichen Zeitaufschlag verbunden sein. Typische Beispiele f\u00fcr diese Aufgaben sind das Einlesen von Daten aus einer Datenbank, die Bearbeitung von Internetanfragen oder die Arbeit mit gro\u00dfen Dateien.<\/p>\n<p>Nebenl\u00e4ufigkeit mit <code>async\/await<\/code> kann dazu beitragen, die Wartezeit bei E\/A-gebundenen Aufgaben zu optimieren, indem die Abfolge der Verarbeitungsschritte freigegeben wird und bei Wartezeiten andere Aufgaben erledigt werden k\u00f6nnen.<\/p>\n<p>Die Verwendung von <code>async\/await<\/code> ist in vielen Python-Anwendungen von Vorteil, z.\u00a0B. in Web-Anwendungen, die h\u00e4ufig mit Datenbanken kommunizieren oder Web-Anfragen bearbeiten. Grafische Bedienoberfl\u00e4chen (GUIs) k\u00f6nnen ebenfalls von <code>async\/await<\/code> profitieren, indem die Ausf\u00fchrung von Hintergrundaufgaben erm\u00f6glicht wird, w\u00e4hrend die Benutzer*innen mit der Anwendung interagieren.<\/p>\n<h3 class=\"wp-block-heading\" id=\"cpu-bound-tasks\">CPU-gebundene Aufgaben<\/h3>\n<p>Eine Aufgabe oder ein Programm gilt als CPU-gebunden, wenn die Ausf\u00fchrungsgeschwindigkeit in erster Linie durch die Geschwindigkeit der CPU begrenzt ist. Typische Beispiele sind die Bild- oder Videobearbeitung, zum Beispiel Gr\u00f6\u00dfenanpassungen oder andere Bildmanipulationen, sowie komplexe mathematische Berechnungen, etwa die Matrixmultiplikation oder das Modelltraining beim maschinellen Lernen.<\/p>\n<p>Im Gegensatz zu E\/A-gebundenen Aufgaben k\u00f6nnen CPU-gebundene Aufgaben nur selten durch <code>async\/await<\/code> optimiert werden, da die CPU mit der Bearbeitung der Aufgaben bereits ausgelastet ist. Wenn Ihr System mehrere CPUs besitzt oder einige der Aufgaben an eine oder mehrere GPUs ausgelagert werden k\u00f6nnen, dann k\u00f6nnen CPU-gebundene Aufgaben schneller erledigt werden, indem mehrere Threads erstellt werden und Multiprocessing verwendet wird. Durch Multiprocessing kann die Auslastung dieser CPUs und GPUs optimiert werden. Dies ist auch der Grund, warum viele Machine-Learning-Systeme und KI-Modelle heutzutage auf Systemen mit vielen GPUs trainiert werden.<\/p>\n<p>Mit reinem Python-Code ist dies jedoch schwer zu bewerkstelligen, da Python so konzipiert ist, dass den Benutzer*innen die Kontrolle \u00fcber die grundlegenden Berechnungsprozesse abgenommen wird. Dar\u00fcber hinaus schr\u00e4nkt der GIL die gemeinsame Nutzung von Ressourcen durch mehrere Python-Threads ein. Seit Python 3.13 ist es jedoch m\u00f6glich, ohne GIL zu arbeiten, und dies erm\u00f6glicht echtes Multithreading. Der GIL \u2013 und die M\u00f6glichkeit, ohne ihn zu arbeiten \u2013 wird das Thema unseres n\u00e4chsten Artikels sein.<\/p>\n<p>Manchmal ist keine der oben genannten Methoden in der Lage, CPU-gebundene Aufgaben zufriedenstellend zu beschleunigen. In solchen F\u00e4llen m\u00fcssen die CPU-gebundenen Aufgaben eventuell in kleinere Aufgaben gegliedert werden, damit sie auf mehrere Threads, mehrere Prozessoren oder gar mehrere Systeme aufgeteilt werden k\u00f6nnen. F\u00fcr diese Art der Parallelverarbeitung m\u00fcssen Sie Ihren Code allerdings m\u00f6glicherweise komplett \u00fcberarbeiten. In Python bietet das Paket <code>multiprocessing<\/code> sowohl lokale als auch Remote-Nebenl\u00e4ufigkeit, mit der die GIL-bedingten Einschr\u00e4nkungen umgangen werden k\u00f6nnen. Auch diesbez\u00fcglich wir werden uns im n\u00e4chsten Blogartikel einige Beispiele ansehen.<\/p>\n<h2 class=\"wp-block-heading\" id=\"debugging-concurrent-code-in-py-charm\">Debuggen von nebenl\u00e4ufigem Code in PyCharm<\/h2>\n<p>Das Debuggen von asynchronem oder nebenl\u00e4ufigem Code kann schwierig sein, da die Programmausf\u00fchrung nicht sequenziell erfolgt, es ist also schwer zu erkennen, wo und wann der Code ausgef\u00fchrt wird. Viele Entwickler*innen verwenden <code>print<\/code>, um den Codefluss nachzuvollziehen. Dieser Ansatz ist jedoch nicht zu empfehlen, da er sehr schwerf\u00e4llig ist und die Untersuchung eines Programms, das Komplexit\u00e4ten wie Nebenl\u00e4ufigkeit aufweist, damit nicht einfach ist. Au\u00dferdem ist es umst\u00e4ndlich, den Code danach aufzur\u00e4umen.<\/p>\n<p>Viele IDEs stellen Debugger bereit, die sich hervorragend zum Untersuchen von Variablen und des Programmablaufs eignen. Debugger stellen auch einen klaren Stack-Trace \u00fcber mehrere Threads hinweg bereit. Sehen wir uns an, wie wir in <a href=\"https:\/\/www.jetbrains.com\/pycharm\/\" data-type=\"link\" data-id=\"https:\/\/www.jetbrains.com\/de-de\/pycharm\/data-science\/\" target=\"_blank\" rel=\"noopener\">PyCharm<\/a> verfolgen k\u00f6nnen, was in unserer Restaurantsimulation mit <code>task_queue<\/code> geschieht.<\/p>\n<p>Zun\u00e4chst setzen wir einige Haltepunkte in unserem Code. Dazu k\u00f6nnen wir auf die Nummer der Zeile klicken, in der der Debugger die Ausf\u00fchrung anhalten soll. Die Zeilennummer wird durch einen roten Punkt ersetzt, um anzuzeigen, dass dort ein Haltepunkt gesetzt ist. Wir setzen Haltepunkte in den Zeilen 23, 27 und 65, wo <code>task_queue<\/code> in verschiedenen Threads ge\u00e4ndert wird.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"720\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-8.png\" alt=\"\" class=\"wp-image-574369\" \/><\/figure>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"554\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-9.png\" alt=\"\" class=\"wp-image-574380\" \/><\/figure>\n<p>Dann k\u00f6nnen wir das Programm im Debug-Modus ausf\u00fchren, indem wir oben rechts auf das kleine K\u00e4fersymbol klicken.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"860\" height=\"258\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-10.png\" alt=\"\" class=\"wp-image-574391\" \/><\/figure>\n<p>Nach Anklicken des Symbols \u00f6ffnet sich das <em>Debug<\/em>-Fenster. Das Programm wird ausgef\u00fchrt, bis es auf den ersten im Code markierten Haltepunkt trifft.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"1002\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-11.png\" alt=\"\" class=\"wp-image-574402\" \/><\/figure>\n<p>Hier sehen wir, dass der Thread <code>John<\/code> versucht, eine Aufgabe abzuholen, und Zeile 65 ist markiert. Zu diesem Zeitpunkt ist die markierte Zeile noch nicht ausgef\u00fchrt worden. Dies ist n\u00fctzlich, wenn wir die Variablen untersuchen wollen, bevor der Code am Haltepunkt ausgef\u00fchrt wird.<\/p>\n<p>Sehen wir uns an, was sich in <code>task_queue<\/code> befindet. Das l\u00e4sst sich ganz einfach bewerkstelligen, indem wir im <em>Debug<\/em>-Fenster wie unten abgebildet mit der Eingabe des Variablennamens beginnen.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"468\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-12.png\" alt=\"\" class=\"wp-image-574414\" \/><\/figure>\n<p>W\u00e4hlen Sie \u201etask_queue\u201c aus oder geben Sie es ein, und dr\u00fccken Sie dann <em>Enter<\/em>. Sie werden sehen, dass sich die Aufgabe <code>take_order<\/code> in der Warteschlange befindet.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"527\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-13.png\" alt=\"\" class=\"wp-image-574425\" \/><\/figure>\n<p>F\u00fchren Sie nun den Haltepunkt aus, indem Sie wie unten abgebildet auf die Schaltfl\u00e4che <em>Step in<\/em> klicken.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1274\" height=\"488\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-14.png\" alt=\"\" class=\"wp-image-574436\" \/><\/figure>\n<p>Nachdem Sie auf diese Schaltfl\u00e4che geklickt und das sich \u00f6ffnende Fenster <em>Special Variables<\/em> untersucht haben, sehen Sie, dass die Aufgabenvariable jetzt <code>take_order<\/code> im Thread <code>John<\/code> ist.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"522\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-15.png\" alt=\"\" class=\"wp-image-574447\" \/><\/figure>\n<p>Wenn Sie sich <code>task_queue<\/code> erneut ansehen, werden Sie feststellen, dass die Liste jetzt leer ist.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"451\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-16.png\" alt=\"\" class=\"wp-image-574458\" \/><\/figure>\n<p>Klicken Sie nun auf die Schaltfl\u00e4che <em>Resume Program<\/em> und lassen Sie das Programm weiterlaufen.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"994\" height=\"610\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-17.png\" alt=\"\" class=\"wp-image-574469\" \/><\/figure>\n<p>Wenn das Programm die Benutzereingabe erreicht, zeigt uns PyCharm das <em>Console<\/em>-Fenster an, um uns die Eingabe zu erm\u00f6glichen. Nehmen wir an, wir m\u00f6chten zwei Burger. Wir geben 2 ein und dr\u00fccken <em>Enter<\/em>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1288\" height=\"616\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-18.png\" alt=\"\" class=\"wp-image-574480\" \/><\/figure>\n<p>Jetzt kommen wir zum zweiten Haltepunkt. Wenn wir auf <em>Threads &amp; Variables<\/em> klicken, um zu diesem Fenster zur\u00fcckzukehren, sehen wir, dass <code>burger_num<\/code> jetzt entsprechend unserer Eingabe den Wert 2 hat.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"1002\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-19.png\" alt=\"\" class=\"wp-image-574491\" \/><\/figure>\n<p>Wir f\u00fchren nun mit einem Einzelschritt den Code am Haltepunkt aus und \u00fcberpr\u00fcfen wie zuvor die Variable <code>task_queue<\/code>. Wir sehen, dass eine Aufgabe <code>make_burger<\/code> hinzugef\u00fcgt wurde.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"405\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-20.png\" alt=\"\" class=\"wp-image-574502\" \/><\/figure>\n<p>Wir lassen das Programm erneut weiterlaufen, und wenn wir nach dem erneuten Anhalten den Haltepunkt mit einem Einzelschritt ausf\u00fchren, sehen wir, dass <code>Jane<\/code> die Aufgabe abholt.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"1002\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-21.png\" alt=\"\" class=\"wp-image-574513\" \/><\/figure>\n<p>Den Rest des Codes k\u00f6nnen Sie nun eigenst\u00e4ndig untersuchen. Wenn Sie fertig sind, klicken Sie einfach auf die rote <em>Stop<\/em>-Schaltfl\u00e4che am oberen Fensterrand.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"848\" height=\"244\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-22.png\" alt=\"\" class=\"wp-image-574524\" \/><\/figure>\n<p>Mit dem Debugger in PyCharm k\u00f6nnen Sie die Ausf\u00fchrung Ihres Programms \u00fcber verschiedene Threads hinweg verfolgen und sehr einfach unterschiedliche Variablen untersuchen.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Fazit<\/h2>\n<p>Jetzt kennen Sie die Grundlagen der Nebenl\u00e4ufigkeit in Python, und ich bin zuversichtlich, dass Sie das Thema mit etwas \u00dcbung meistern werden. Im n\u00e4chsten Blogartikel werfen wir einen Blick auf den GIL in Python, die Rolle, die dieser spielt, und was sich \u00e4ndert, wenn er nicht vorhanden ist.<\/p>\n<p>PyCharm stellt leistungsstarke Werkzeuge f\u00fcr die Arbeit mit nebenl\u00e4ufigem Python-Code bereit. Wie in diesem Blogartikel gezeigt, erm\u00f6glicht der Debugger das schrittweise Untersuchen von asynchron oder in Threads ausgef\u00fchrtem Code und hilft Ihnen damit, den Ausf\u00fchrungsfluss zu verfolgen, gemeinsame Ressourcen zu \u00fcberwachen und Probleme zu erkennen. Mit intuitiven Haltepunkten, der Anzeige von Variableninhalten in Echtzeit, nahtloser Einbindung der Konsole f\u00fcr Benutzereingaben und einer robusten Protokollierungsunterst\u00fctzung erleichtert Ihnen PyCharm das Schreiben, Testen und Debuggen von Anwendungen.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\">\n                                                <a href=\"https:\/\/www.jetbrains.com\/de-de\/pycharm\/\" class=\"btn\" target=\"\" rel=\"noopener\">PyCharm jetzt herunterladen<\/a><\/div>\n<\/div>\n<p><strong>Autorin des urspr\u00fcnglichen Blogposts<\/strong><\/p>\n","protected":false},"author":811,"featured_media":576193,"comment_status":"closed","ping_status":"closed","template":"","categories":[1401,2347,8377],"tags":[6939,5377],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm\/576190"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/types\/pycharm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/users\/811"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/comments?post=576190"}],"version-history":[{"count":4,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm\/576190\/revisions"}],"predecessor-version":[{"id":576217,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm\/576190\/revisions\/576217"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media\/576193"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media?parent=576190"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/categories?post=576190"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/tags?post=576190"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/cross-post-tag?post=576190"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}