{"id":591263,"date":"2025-08-18T13:27:42","date_gmt":"2025-08-18T12:27:42","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=591263"},"modified":"2025-10-16T11:06:03","modified_gmt":"2025-10-16T10:06:03","slug":"schnelleres-python-entfernen-des-global-interpreter-lock-in-python","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/de\/pycharm\/2025\/08\/schnelleres-python-entfernen-des-global-interpreter-lock-in-python\/","title":{"rendered":"Schnelleres Python: Entfernen des Global Interpreter Lock in Python"},"content":{"rendered":"<h2 id=\"what-is-pythons-global-interpreter-lock-gil\" class=\"wp-block-heading\">Was ist der Global Interpreter Lock (GIL) in Python?<\/h2>\n<p>Der \u201eGlobal Interpreter Lock\u201c (oder \u201eGIL\u201c) ist ein g\u00e4ngiger Begriff in der Python-Community. Es handelt sich um ein allgemein bekanntes Merkmal von Python. Aber was genau ist der GIL?<\/p>\n<p>Wenn Sie Erfahrung mit anderen Programmiersprachen (z.\u00a0B. Rust) haben, wissen Sie vielleicht schon, was ein Mutex ist. Es steht f\u00fcr \u201emutual exclusion\u201c, also \u201egegenseitiger Ausschluss\u201c. Ein Mutex sorgt daf\u00fcr, dass immer nur ein Thread auf die Daten zugreifen kann. Dadurch wird verhindert, dass Daten von mehreren Threads gleichzeitig ge\u00e4ndert werden. Sie k\u00f6nnen es sich als eine Art \u201eSchloss\u201c vorstellen. Dieses blockiert den Zugriff aller Threads auf die Daten, mit Ausnahme des einen Threads, der den Schl\u00fcssel besitzt.<\/p>\n<p>Der GIL ist technisch betrachtet ein Mutex. Er erlaubt jeweils nur einem Thread den Zugriff auf den Python-Interpreter. Ich stelle ihn mir manchmal wie das Steuer eines Autos vor. Sie wollen nie mehr als eine Person am Steuer haben! Andererseits wechseln sich bei einer Reise oft mehrere Personen am Steuer ab. In etwa so wird auch der Zugriff auf den Interpreter an einen anderen Thread abgegeben.<\/p>\n<p>Aufgrund des GIL sind in Python keine echten Multithreading-Prozesse m\u00f6glich. Diese Funktion hat im Verlauf des letzten Jahrzehnts Debatten ausgel\u00f6st, und es gab viele Versuche, Python schneller zu machen, indem der GIL entfernt und Multithreading-Prozesse erm\u00f6glicht wurden. K\u00fcrzlich hat Python 3.13 eine Option eingef\u00fchrt, mit der wir Python ohne GIL verwenden k\u00f6nnen \u2013 dies wird manchmal auch als No-GIL-Python oder Free-Threaded-Python bezeichnet, also Python mit freiem Threading. Dies l\u00e4utet eine neue \u00c4ra in der Python-Programmierung ein.<\/p>\n<h2 id=\"why-was-the-gil-there-in-the-first-place\" class=\"wp-block-heading\">Warum gibt es den GIL \u00fcberhaupt?<\/h2>\n<p>Wenn der GIL so unbeliebt ist, warum wurde er \u00fcberhaupt eingef\u00fchrt? Der GIL hat in Wirklichkeit auch Vorteile. In anderen Programmiersprachen mit echtem Multithreading kommt es manchmal zu Problemen, wenn mehr als ein Thread Daten modifiziert, denn das Endergebnis h\u00e4ngt davon ab, welcher Thread oder Prozess zuerst fertig wird. Dies wird als \u201eRace-Condition\u201c bezeichnet. Sprachen wie Rust sind oft schwer zu erlernen, da beim Programmieren Mutexe verwendet werden m\u00fcssen, um Race-Conditions zu verhindern.<\/p>\n<p>In Python haben alle Objekte einen Referenzz\u00e4hler, um Buch dar\u00fcber zu f\u00fchren, wie viele andere Objekte Informationen von ihnen ben\u00f6tigen. Wenn der Referenzz\u00e4hler null erreicht, k\u00f6nnen wir sicher davon ausgehen, dass das Objekt nicht mehr ben\u00f6tigt wird und sein Speicher freigegeben werden kann, denn in Python gibt es ja aufgrund des GIL keine Race-Conditions.<\/p>\n<p>Als Python 1991 ver\u00f6ffentlicht wurde, hatten die meisten PCs nur einen CPU-Kern, und nur wenige Programmierer forderten eine Unterst\u00fctzung von Multithreading. Mit einem GIL lassen sich viele Probleme bei der Programmimplementierung l\u00f6sen, und auch die Codewartung wird erleichtert. Daher hat Python-Erfinder Guido van Rossum Python im Jahr 1992 mit einem GIL ausgestattet.<\/p>\n<p>Im Jahr 2025 stellt sich die Lage ganz anders dar: PCs haben Mehrkernprozessoren und damit viel mehr Rechenleistung. Wir k\u00f6nnen diese zus\u00e4tzliche Leistung nutzen, um echte Nebenl\u00e4ufigkeit zu erreichen, ohne den GIL einzustampfen.<\/p>\n<p>Sp\u00e4ter in diesem Beitrag werden Sie erfahren, wie Sie den GIL loswerden k\u00f6nnen. Aber erst einmal sehen wir uns an, wie echte Nebenl\u00e4ufigkeit mit GIL funktioniert.<\/p>\n<h2 id=\"multiprocessing-in-python\" class=\"wp-block-heading\">Multiprocessing in Python<\/h2>\n<p>Bevor wir uns dem Entfernen des GIL zuwenden, sehen wir uns an, wie sich bei der Python-Entwicklung mit der multiprocessing-Bibliothek echte Nebenl\u00e4ufigkeit erreichen l\u00e4sst. Die multiprocessing-Standardbibliothek bietet sowohl lokale als auch Remote-Nebenl\u00e4ufigkeit. Der Global Interpreter Lock wird dabei durch die Verwendung von Subprozessen anstelle von Threads umgangen. Auf diese Weise k\u00f6nnen Entwickler*innen mit dem multiprocessing-Modul mehrere Prozessoren auf einem System voll ausnutzen.<\/p>\n<p>Um unser Programm jedoch multiprocessing-f\u00e4hig zu machen, m\u00fcssen wir es etwas anpassen. Sehen Sie sich das folgende Beispiel f\u00fcr die Verwendung der multiprocessing-Bibliothek in Python an.<br \/>Sie erinnern sich sicher an unser asynchrones Burger-Restaurant aus <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/06\/concurrency-in-async-await-and-threading\/\">Teil 1 dieser Blogreihe<\/a>:<\/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    print(f\"Preparing burger #{order_num}...\")\n\n    await asyncio.sleep(5) # time for making the burger\n\n    print(f\"Burger made #{order_num}\")\n\nasync def main():\n\n    order_queue = []\n\n    for i in range(3):\n\n        order_queue.append(make_burger(i))\n\n    await asyncio.gather(*(order_queue))\n\nif __name__ == \"__main__\":\n\n    s = time.perf_counter()\n\n    asyncio.run(main())\n\n    elapsed = time.perf_counter() - s\n\n    print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>F\u00fcr denselben Zweck k\u00f6nnen wir auch die multiprocessing-Bibliothek verwenden:<\/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 multiprocessing\n\n\nimport time\n\n\ndef make_burger(order_num):\n\n\n   print(f\"Preparing burger #{order_num}...\")\n\n\n   time.sleep(5) # time for making the burger\n\n\n   print(f\"Burger made #{order_num}\")\n\n\n\n\nif __name__ == \"__main__\":\n\n\n   print(\"Number of available CPU:\", multiprocessing.cpu_count())\n\n\n\n\n   s = time.perf_counter()\n\n\n   all_processes = []\n\n\n   for i in range(3):\n       process = multiprocessing.Process(target=make_burger, args=(i,))\n       process.start()\n       all_processes.append(process)\n\n\n   for process in all_processes:\n       process.join()\n\n\n   elapsed = time.perf_counter() - s\n\n\n   print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>Wie Sie sich vielleicht erinnern, sind zahlreiche Methoden im multiprocessing-Modul denen im threading-Modul sehr \u00e4hnlich. Um den Unterschied durch Multiprocessing zu erkennen, sehen wir uns einen komplexeren Anwendungsfall an:<\/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 multiprocessing\nimport time\nimport queue\n\n\n\n\ndef make_burger(order_num, item_made):\n   name = multiprocessing.current_process().name\n   print(f\"{name} is preparing burger #{order_num}...\")\n   time.sleep(5)  # time for making burger\n   item_made.put(f\"Burger #{order_num}\")\n   print(f\"Burger #{order_num} made by {name}\")\n\n\n\n\ndef make_fries(order_num, item_made):\n   name = multiprocessing.current_process().name\n   print(f\"{name} is preparing fries #{order_num}...\")\n   time.sleep(2)  # time for making fries\n   item_made.put(f\"Fries #{order_num}\")\n   print(f\"Fries #{order_num} made by {name}\")\n\n\n\n\ndef working(task_queue, item_made, order_num, lock):\n   break_count = 0\n   name = multiprocessing.current_process().name\n   while True:\n       try:\n           task = task_queue.get_nowait()\n       except queue.Empty:\n           print(f\"{name} has nothing to do...\")\n           if break_count &gt; 1:\n               break  # stop if idle for too long\n           else:\n               break_count += 1\n           time.sleep(1)\n       else:\n           lock.acquire()\n           try:\n               current_num = order_num.value\n               order_num.value = current_num + 1\n           finally:\n               lock.release()\n           task(current_num, item_made)\n           break_count = 0\n\n\n\n\nif __name__ == \"__main__\":\n\n\n   print(\"Welcome to Pyburger! Please place your order.\")\n\n\n   burger_num = input(\"Number of burgers:\")\n   fries_num = input(\"Number of fries:\")\n\n\n   s = time.perf_counter()\n\n\n   task_queue = multiprocessing.Queue()\n   item_made = multiprocessing.Queue()\n   order_num = multiprocessing.Value(\"i\", 0)\n   lock = multiprocessing.Lock()\n\n\n   for i in range(int(burger_num)):\n       task_queue.put(make_burger)\n   for i in range(int(fries_num)):\n       task_queue.put(make_fries)\n\n\n   staff1 = multiprocessing.Process(\n       target=working,\n       name=\"John\",\n       args=(\n           task_queue,\n           item_made,\n           order_num,\n           lock,\n       ),\n   )\n   staff2 = multiprocessing.Process(\n       target=working,\n       name=\"Jane\",\n       args=(\n           task_queue,\n           item_made,\n           order_num,\n           lock,\n       ),\n   )\n\n\n   staff1.start()\n   staff2.start()\n\n\n   staff1.join()\n   staff2.join()\n\n\n   print(\"All tasks finished. Closing now.\")\n   print(\"Items created are:\")\n\n\n   while not item_made.empty():\n       print(item_made.get())\n\n\n   elapsed = time.perf_counter() - s\n\n\n   print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>Hier ist die Ausgabe, die wir erhalten:<\/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=\"\">Welcome to Pyburger! Please place your order.\nNumber of burgers:3\nNumber of fries:2\nJane has nothing to do...\nJohn is preparing burger #0...\nJane is preparing burger #1...\nBurger #0 made by John\nJohn is preparing burger #2...\nBurger #1 made by Jane\nJane is preparing fries #3...\nFries #3 made by Jane\nJane is preparing fries #4...\nBurger #2 made by John\nJohn has nothing to do...\nFries #4 made by Jane\nJane has nothing to do...\nJohn has nothing to do...\nJane has nothing to do...\nJohn has nothing to do...\nJane has nothing to do...\nAll tasks finished. Closing now.\nItems created are:\nBurger #0\nBurger #1\nFries #3\nBurger #2\nFries #4\nOrders completed in 12.21 seconds.\n<\/pre>\n<p>Beachten Sie, dass es beim Multiprocessing einige Einschr\u00e4nkungen gibt, die es erfordern, den obigen Code auf diese Weise zu programmieren. Sehen wir sie uns der Reihe nach an.<\/p>\n<p>Wie Sie sich erinnern, hatten wir zuvor die Funktionen make_burger und make_fries, um eine Funktion mit der richtigen order_num zu generieren:<\/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 make_burger(order_num):\n   def making_burger():\n       logger.info(f\"Preparing burger #{order_num}...\")\n       time.sleep(5)  # time for making burger\n       logger.info(f\"Burger made #{order_num}\")\n\n\n   return making_burger\n\n\n\n\ndef make_fries(order_num):\n   def making_fries():\n       logger.info(f\"Preparing fries #{order_num}...\")\n       time.sleep(2)  # time for making fries\n       logger.info(f\"Fries made #{order_num}\")\n\n\n   return making_fries\n<\/pre>\n<p>Bei der Verwendung von Multiprocessing geht das nicht. Wenn wir es versuchen, erhalten wir eine Fehlermeldung, die ungef\u00e4hr so aussieht:<\/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=\"\">AttributeError: Can't get local object 'make_burger..making_burger'<\/pre>\n<p>Das liegt daran, dass Multiprocessing <a href=\"https:\/\/docs.python.org\/3\/library\/pickle.html\" target=\"_blank\" rel=\"noopener\">pickle<\/a> verwendet, das im Allgemeinen nur Funktionen auf der obersten Modulebene serialisieren kann. Dies ist eine der Einschr\u00e4nkungen von Multiprocessing<em>.<\/em><\/p>\n<p>Zweitens wird es Ihnen im obigen Codeabschnitt, der Multiprocessing verwendet, aufgefallen sein, dass wir keine globalen Variablen f\u00fcr das Teilen von Daten verwenden. Zum Beispiel k\u00f6nnen wir keine globalen Variablen f\u00fcr item_made und order_num verwenden. F\u00fcr den Datenaustausch zwischen unterschiedlichen Prozessen werden spezielle Klassenobjekte wie <em>Queue<\/em> und <em>Value<\/em> aus der multiprocessing-Bibliothek verwendet und als Argumente an die Prozesse \u00fcbergeben.<br \/>Ganz allgemein wird das Teilen von Daten und Zust\u00e4nden zwischen unterschiedlichen Prozessen nicht empfohlen, da dies zahlreiche weitere Probleme verursachen kann. In unserem obigen Beispiel m\u00fcssen wir einen Lock verwenden, um sicherzustellen, dass jeweils nur ein Prozess auf den Wert von order_num zugreifen und ihn erh\u00f6hen kann. Ohne den Lock kann die Anzahl der Bestellungen auf folgende Weise durcheinander geraten:<\/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=\"\">Items created are:\n\nBurger #0\nBurger #0\nFries #2\nBurger #1\nFries #3\n<\/pre>\n<p>So wird ein Lock verwendet, um Probleme zu vermeiden:<\/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=\"\">           lock.acquire()\n           try:\n               current_num = order_num.value\n               order_num.value = current_num + 1\n           finally:\n               lock.release()\n           task(current_num, item_made)\n<\/pre>\n<p>Weitere Informationen zur Verwendung der multiprocessing-Standardbibliothek finden Sie in der <a href=\"https:\/\/docs.python.org\/3\/library\/multiprocessing.html\" target=\"_blank\" rel=\"noopener\">Dokumentation<\/a>.<\/p>\n<h2 id=\"removing-the-gil\" class=\"wp-block-heading\">Entfernen des GIL<\/h2>\n<p>Das Entfernen des GIL wird seit fast einem Jahrzehnt diskutiert. 2016 pr\u00e4sentierte Larry Hastings auf dem Python Language Summit seine \u00dcberlegungen zu einer \u201eGIL-Ektomie\u201c am CPython-Interpreter und die Fortschritte, die er mit dieser Idee erzielt hatte [1]. Dies war ein erster Versuch, den GIL aus Python zu entfernen. 2021 entfachte Sam Gross die Diskussion \u00fcber die Entfernung der GIL neu [2], und das f\u00fchrte im Jahr 2023 zum Python-Verbesserungsvorschlag PEP 703: <a href=\"https:\/\/peps.python.org\/pep-0703\/\" target=\"_blank\" rel=\"noopener\">Making the Global Interpreter Lock Optional in CPython<\/a>.<\/p>\n<p>Wie wir erkennen k\u00f6nnen, ist die Entfernung des GIL keineswegs eine \u00fcbereilte Entscheidung und war innerhalb der Community Gegenstand erheblicher Debatten. Wie die obigen Multiprocessing-Beispiele (und der oben verlinkte PEP 703) zeigen, werden die Dinge schnell kompliziert, wenn die Absicherung durch den GIL wegf\u00e4llt.<\/p>\n<p>[1]: <a href=\"https:\/\/lwn.net\/Articles\/689548\/\" target=\"_blank\" rel=\"noopener\">https:\/\/lwn.net\/Articles\/689548\/<\/a><\/p>\n<p>[2]: https:\/\/lwn.net\/ml\/python-dev\/CAGr09bSrMNyVNLTvFq-h6t38kTxqTXfgxJYApmbEWnT71L74-g@mail.gmail.com\/<\/p>\n<h3 id=\"reference-counting\" class=\"wp-block-heading\">Referenzz\u00e4hlung<\/h3>\n<p>Wenn der GIL vorhanden ist, sind Referenzz\u00e4hlung und Garbage Collection einfacher. Wenn jeweils nur ein Thread Zugriff auf Python-Objekte hat, k\u00f6nnen wir uns mit einer einfachen, nicht-atomaren <a href=\"https:\/\/en.wikipedia.org\/wiki\/Reference_counting\" target=\"_blank\" rel=\"noopener\">Referenzz\u00e4hlung<\/a> begn\u00fcgen und das Objekt zum L\u00f6schen freigeben, wenn der Referenzz\u00e4hler null erreicht.<\/p>\n<p>Ohne GIL ist die Sache komplizierter. Eine nicht-atomare Referenzz\u00e4hlung ist nicht mehr m\u00f6glich, da diese keine <a href=\"https:\/\/en.wikipedia.org\/wiki\/Thread_safety\" target=\"_blank\" rel=\"noopener\">Threadsicherheit<\/a> bietet. Wenn mehrere Threads gleichzeitig den Referenzz\u00e4hler eines Python-Objekts erh\u00f6hen oder erniedrigen, kann das schiefgehen. Im Idealfall sollte eine atomare Referenzz\u00e4hlung verwendet werden, um die Threadsicherheit zu gew\u00e4hrleisten. Diese Methode ist jedoch mit einem hohen Overhead verbunden, und bei zahlreichen Threads leidet daher die Effizienz.<\/p>\n<p>Die L\u00f6sung besteht in der Verwendung einer teilweise atomaren Referenzz\u00e4hlung (\u201ebiased reference counting\u201c), die ebenfalls threadsicher ist. Die Idee dahinter ist, jedes Objekt einem Besitzerthread zuzuordnen, also dem Thread, der am h\u00e4ufigsten auf das Objekt zugreift. Besitzerthreads k\u00f6nnen eine nicht-atomare Referenzz\u00e4hlung f\u00fcr die ihnen zugeordneten Objekte durchf\u00fchren, w\u00e4hrend andere Threads bei diesen Objekten zu einer atomaren Referenzz\u00e4hlung gezwungen werden. Diese Methode ist der einfachen atomaren Referenzz\u00e4hlung vorzuziehen, da auf die meisten Objekte die meiste Zeit nur von einem Thread aus zugegriffen wird. Wir k\u00f6nnen den Overhead bei der Ausf\u00fchrung reduzieren, indem wir dem Besitzerthread eine nicht-atomare Referenzz\u00e4hlung erlauben.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586289\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-22.png\" alt=\"\" width=\"1280\" height=\"720\" \/><\/figure>\n<p>Au\u00dferdem werden einige h\u00e4ufig verwendete Python-Objekte, wie <em>True, False<\/em>, kleine Ganzzahlen und einige internalisierte Zeichenfolgen, \u201eunsterblich\u201c gemacht. \u201eUnsterblich\u201c bedeutet in diesem Zusammenhang lediglich, dass die Objekte w\u00e4hrend der gesamten Lebensdauer des Programms im Speicher verbleiben und somit keine Referenzz\u00e4hlung erforderlich ist.<\/p>\n<h3 id=\"garbage-collection\" class=\"wp-block-heading\">Garbage Collection<\/h3>\n<p>Wir m\u00fcssen auch die Art und Weise \u00e4ndern, wie durch <a href=\"https:\/\/en.wikipedia.org\/wiki\/Garbage_collection_(computer_science)\" target=\"_blank\" rel=\"noopener\">Garbage Collection<\/a> Speicher freigegeben wird. Anstatt den Referenzz\u00e4hler sofort zu erniedrigen, wenn eine Referenz freigegeben wird, und das Objekt sofort zu entfernen, wenn der Referenzz\u00e4hler null erreicht, wird die sogenannte \u201everz\u00f6gerte Referenzz\u00e4hlung\u201c verwendet.<\/p>\n<p>Wenn der Referenzz\u00e4hler erniedrigt werden soll, wird das Objekt in einer Tabelle gespeichert. Diese Tabelle wird dann nochmals \u00fcberpr\u00fcft, um festzustellen, ob das Erniedrigen des Referenzz\u00e4hlers korrekt ist oder nicht. Dadurch wird vermieden, dass das Objekt vorzeitig entfernt wird, w\u00e4hrend es noch referenziert ist, was ohne GIL passieren kann, da die Referenzz\u00e4hlung nicht so einfach ist wie mit GIL. Dies macht den Garbage-Collection-Prozess komplexer, da die Garbage Collection unter Umst\u00e4nden den <a href=\"https:\/\/en.wikipedia.org\/wiki\/Stack_(abstract_data_type)\" target=\"_blank\" rel=\"noopener\">Stack<\/a> jedes Threads durchlaufen muss, um den Referenzz\u00e4hler der einzelnen Threads zu erhalten.<\/p>\n<p>Eine weitere Sache, die wir ber\u00fccksichtigen m\u00fcssen: Der Referenzz\u00e4hler muss w\u00e4hrend der Garbage Collection unver\u00e4ndert bleiben. Wenn ein zu l\u00f6schendes Objekt pl\u00f6tzlich referenziert wird, f\u00fchrt dies zu ernsthaften Problemen. Aus diesem Grund muss w\u00e4hrend des Garbage-Collection-Zyklus \u201edie Welt angehalten\u201c werden, um die Threadsicherheit zu gew\u00e4hrleisten.<\/p>\n<h3 id=\"memory-allocation\" class=\"wp-block-heading\">Speicherallokation<\/h3>\n<p>Wenn ein vorhandener GIL die Threadsicherheit garantiert, wird der Python-interne Speicherallokator pymalloc verwendet. Ohne GIL brauchen wir jedoch einen neuen Speicherallokator. Sam Gross schlug im PEP <a href=\"https:\/\/github.com\/microsoft\/mimalloc\" target=\"_blank\" rel=\"noopener\">mimalloc<\/a> vor, einen universellen Allokator, der von Daan Leijen entwickelt und von Microsoft gepflegt wird. Dieser ist eine gute Wahl, da er threadsicher ist und bei kleinen Objekten eine gute Performance bietet.<\/p>\n<p>Mimalloc f\u00fcllt seinen Heap mit Seiten und die Seiten mit Bl\u00f6cken<em>.<\/em> Jede Seite enth\u00e4lt Bl\u00f6cke, und die Bl\u00f6cke innerhalb jeder Seite sind alle gleich gro\u00df. Durch das Einf\u00fchren einiger Einschr\u00e4nkungen in Bezug auf den Zugriff auf list- und dict-Objekte muss der Garbage Collector keine <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linked_list\" target=\"_blank\" rel=\"noopener\">verkn\u00fcpfte Liste<\/a> f\u00fchren, um alle Objekte zu finden, und auch der Lesezugriff auf list- und dict-Objekte ist ohne das Belegen des Locks m\u00f6glich.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586300\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-23.png\" alt=\"\" width=\"1280\" height=\"720\" \/><\/figure>\n<p>In Bezug auf das Entfernen des GIL gibt es noch weitere Details zu ber\u00fccksichtigen \u2013 wir k\u00f6nnen hier unm\u00f6glich auf alle eingehen. Eine vollst\u00e4ndige Liste finden Sie in <a href=\"https:\/\/peps.python.org\/pep-0703\/\" target=\"_blank\" rel=\"noopener\">PEP 703 &#8211; Making the Global Interpreter Lock Optional in CPython<\/a>.<\/p>\n<h2 id=\"difference-in-performance-with-and-without-the-gil\" class=\"wp-block-heading\">Performanceunterschiede mit und ohne GIL<\/h2>\n<p>Da Python 3.13 optional freies Threading erlaubt, k\u00f6nnen wir die Performance der Standardversion von Python 3.13 mit der Free-Threaded-Version vergleichen.<\/p>\n<h3 id=\"install-thread-free-python\" class=\"wp-block-heading\">Python mit freiem Threading installieren<\/h3>\n<p>Wir verwenden <a href=\"https:\/\/github.com\/pyenv\/pyenv\" target=\"_blank\" rel=\"noopener\">pyenv<\/a>, um beide Versionen zu installieren: die Standardversion (z.\u00a0B. 3.13.5) und die Free-Threaded-Version (z.\u00a0B. 3.13.5t).<\/p>\n<p>Alternativ k\u00f6nnen Sie auch die Installationsprogramme auf <a href=\"http:\/\/python.org\" target=\"_blank\" rel=\"noopener\">python.org<\/a> verwenden. Achten Sie darauf, w\u00e4hrend der Installation die Option <em>Customize<\/em> auszuw\u00e4hlen, und klicken Sie auf das zus\u00e4tzliche Kontrollk\u00e4stchen, um die Installation von Free-Threaded-Python zu aktivieren (<a href=\"https:\/\/til.simonwillison.net\/python\/trying-free-threaded-python\" target=\"_blank\" rel=\"noopener\">wie im Beispiel in diesem Blogartikel<\/a>).<\/p>\n<p>Nachdem wir beide Versionen installiert haben, k\u00f6nnen wir sie als Interpreter zu einem PyCharm-Projekt hinzuf\u00fcgen.<\/p>\n<p>Klicken Sie zun\u00e4chst auf den Namen Ihres Python-Interpreters unten rechts.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586311\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-24.png\" alt=\"\" width=\"960\" height=\"328\" \/><\/figure>\n<p>W\u00e4hlen Sie im Men\u00fc <em>Add New Interpreter<\/em> und dann <em>Add Local Interpreter<\/em> aus.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586322\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-25.png\" alt=\"\" width=\"1600\" height=\"575\" \/><\/figure>\n<p>W\u00e4hlen Sie <em>Select existing<\/em>, warten Sie, bis der Interpreter-Pfad geladen ist (was eine Weile dauern kann, wenn Sie wie ich viele Interpreter haben), und w\u00e4hlen Sie dann den soeben installierten neuen Interpreter im Dropdown <em>Python path<\/em> aus.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586333\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-26.png\" alt=\"\" width=\"1538\" height=\"1100\" \/><\/figure>\n<p>Klicken Sie auf <em>OK<\/em>, um ihn hinzuzuf\u00fcgen. Wiederholen Sie die gleichen Schritte, um auch den anderen Interpreter hinzuzuf\u00fcgen. Wenn Sie jetzt wieder auf den Interpreter-Namen unten rechts klicken, sehen Sie mehrere Python-3.13-Interpreter, genau wie im Bild oben.<\/p>\n<h3 id=\"testing-with-a-cpu-bounded-process\" class=\"wp-block-heading\">Testen mit einem CPU-limitierten Prozess<\/h3>\n<p>Als N\u00e4chstes ben\u00f6tigen wir ein Skript, um die unterschiedlichen Versionen zu testen. Wie Sie sich erinnern werden, haben wir in <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/06\/concurrency-in-async-await-and-threading\/#cpu-bound-tasks\">Teil 1 dieser Blogreihe<\/a> gekl\u00e4rt, dass wir echtes Multithreading ben\u00f6tigen, um CPU-limitierte Prozesse zu beschleunigen. Um zu sehen, ob das Entfernen des GIL echtes Multithreading erm\u00f6glicht und Python beschleunigt, k\u00f6nnen wir einen Test mit einem CPU-limitierten Prozess und mehreren Threads durchf\u00fchren. Hier ist das Skript, dessen Generierung ich an <a href=\"https:\/\/www.jetbrains.com\/junie\/\" target=\"_blank\" rel=\"noopener\">Junie<\/a> delegiert habe (mit einigen abschlie\u00dfenden Anpassungen meinerseits):<\/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\nimport multiprocessing  # Kept for CPU count\nfrom concurrent.futures import ThreadPoolExecutor\nimport sys\n\n\n\n\ndef is_prime(n):\n   \"\"\"Check if a number is prime (CPU-intensive operation).\"\"\"\n   if n &lt;= 1:\n       return False\n   if n &lt;= 3:\n       return True\n   if n % 2 == 0 or n % 3 == 0:\n       return False\n   i = 5\n   while i * i &lt;= n:\n       if n % i == 0 or n % (i + 2) == 0:\n           return False\n       i += 6\n   return True\n\n\n\n\ndef count_primes(start, end):\n   \"\"\"Count prime numbers in a range.\"\"\"\n   count = 0\n   for num in range(start, end):\n       if is_prime(num):\n           count += 1\n   return count\n\n\n\n\ndef run_single_thread(range_size, num_chunks):\n   \"\"\"Run the prime counting task in a single thread.\"\"\"\n   chunk_size = range_size \/\/ num_chunks\n   total_count = 0\n\n\n   start_time = time.time()\n\n\n   for i in range(num_chunks):\n       start = i * chunk_size + 1\n       end = (i + 1) * chunk_size + 1 if i &lt; num_chunks - 1 else range_size + 1\n       total_count += count_primes(start, end)\n\n\n   end_time = time.time()\n\n\n   return total_count, end_time - start_time\n\n\n\n\ndef thread_task(start, end):\n   \"\"\"Task function for threads.\"\"\"\n   return count_primes(start, end)\n\n\n\n\ndef run_multi_thread(range_size, num_threads, num_chunks):\n   \"\"\"Run the prime counting task using multiple threads.\"\"\"\n   chunk_size = range_size \/\/ num_chunks\n   total_count = 0\n\n\n   start_time = time.time()\n\n\n   with ThreadPoolExecutor(max_workers=num_threads) as executor:\n       futures = []\n       for i in range(num_chunks):\n           start = i * chunk_size + 1\n           end = (i + 1) * chunk_size + 1 if i &lt; num_chunks - 1 else range_size + 1\n           futures.append(executor.submit(thread_task, start, end))\n\n\n       for future in futures:\n           total_count += future.result()\n\n\n   end_time = time.time()\n\n\n   return total_count, end_time - start_time\n\n\n\n\ndef main():\n   # Fixed parameters\n   range_size = 1000000  # Range of numbers to check for primes\n   num_chunks = 16       # Number of chunks to divide the work into\n   num_threads = 4       # Fixed number of threads for multi-threading test\n\n\n   print(f\"Python version: {sys.version}\")\n   print(f\"CPU count: {multiprocessing.cpu_count()}\")\n   print(f\"Range size: {range_size}\")\n   print(f\"Number of chunks: {num_chunks}\")\n   print(\"-\" * 60)\n\n\n   # Run single-threaded version as baseline\n   print(\"Running single-threaded version (baseline)...\")\n   count, single_time = run_single_thread(range_size, num_chunks)\n   print(f\"Found {count} primes in {single_time:.4f} seconds\")\n   print(\"-\" * 60)\n\n\n   # Run multi-threaded version with fixed number of threads\n   print(f\"Running multi-threaded version with {num_threads} threads...\")\n   count, thread_time = run_multi_thread(range_size, num_threads, num_chunks)\n   speedup = single_time \/ thread_time\n   print(f\"Found {count} primes in {thread_time:.4f} seconds (speedup: {speedup:.2f}x)\")\n   print(\"-\" * 60)\n\n\n   # Summary\n   print(\"SUMMARY:\")\n   print(f\"{'Threads':&lt;10} {'Time (s)':&lt;12} {'Speedup':&lt;10}\")\n   print(f\"{'1 (baseline)':&lt;10} {single_time:&lt;12.4f} {'1.00x':&lt;10}\")\n   print(f\"{num_threads:&lt;10} {thread_time:&lt;12.4f} {speedup:.2f}x\")\n\n\nif __name__ == \"__main__\":\n   main()\n<\/pre>\n<p>Um die Ausf\u00fchrung des Skripts mit unterschiedlichen Python-Interpretern zu erleichtern, k\u00f6nnen wir unserem PyCharm-Projekt ein benutzerdefiniertes Run-Skript hinzuf\u00fcgen.<\/p>\n<p>W\u00e4hlen Sie oben <em>Edit Configurations\u2026<\/em> im Dropdown-Men\u00fc neben der <em>Run<\/em>-Schaltfl\u00e4che (<img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/10\/AD_4nXcRz0HogcsseQcC85-vSwRJhBWhiZCysBUZjcuu1uQYusTx-hj6h-l4FAs_HUE1gcJV5kjSjPuUu1opfKgu6sH5G9epkDlMMw0fBRPyReunr-DgSpg5O_6QTIYROqOaiMHGAX42vg-2.png\" width=\"30\" height=\"23\" \/>) aus.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/10\/AD_4nXeLJtj8xjnN-WQM7UQAop3RCg1hSElHa9WqFMBgXWGyxaxg07kOEhYnBV2i1WOnw5fUB_LsN-QWXidRJ-WMUVAkjF9aSVws684Jbnnoc8P-t_EDD622RghKpL5LjckKoKf251If3A-2.png\" width=\"391\" height=\"238\" \/><\/p>\n<p>Klicken Sie auf die Schaltfl\u00e4che + oben links und w\u00e4hlen Sie dann im Dropdown <em>Add New Configuration<\/em> Python aus.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586344\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-27.png\" alt=\"\" width=\"680\" height=\"1130\" \/><\/figure>\n<p>W\u00e4hlen Sie einen Namen, an dem Sie erkennen k\u00f6nnen, welcher Interpreter verwendet wird, z.\u00a0B. 3.13.5 bzw. 3.15.3t. W\u00e4hlen Sie den richtigen Interpreter aus und geben Sie den Namen des Testskripts wie folgt an:<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-586355\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-28.png\" alt=\"\" width=\"1600\" height=\"1148\" \/><\/figure>\n<p>Erstellen Sie zwei Konfigurationen, eine f\u00fcr jeden Interpreter. Klicken Sie dann auf <em>OK<\/em>.<\/p>\n<p>Jetzt k\u00f6nnen wir das Skript unkompliziert mit oder ohne GIL ausf\u00fchren, indem wir die entsprechende Konfiguration ausw\u00e4hlen und oben auf die Schaltfl\u00e4che <em>Run<\/em> (<img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/10\/AD_4nXcRz0HogcsseQcC85-vSwRJhBWhiZCysBUZjcuu1uQYusTx-hj6h-l4FAs_HUE1gcJV5kjSjPuUu1opfKgu6sH5G9epkDlMMw0fBRPyReunr-DgSpg5O_6QTIYROqOaiMHGAX42vg-2.png\" width=\"30\" height=\"23\" \/>) klicken.<\/p>\n<h3 id=\"comparing-the-results\" class=\"wp-block-heading\">Vergleich der Ergebnisse<\/h3>\n<p>Beim Ausf\u00fchren der Standardversion 3.13.5 mit GIL habe ich folgende Ergebnisse erhalten:<\/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=\"\">Python version: 3.13.5 (main, Jul 10 2025, 20:33:15) [Clang 17.0.0 (clang-1700.0.13.5)]\nCPU count: 8\nRange size: 1000000\nNumber of chunks: 16\n------------------------------------------------------------\nRunning single-threaded version (baseline)...\nFound 78498 primes in 1.1930 seconds\n------------------------------------------------------------\nRunning multi-threaded version with 4 threads...\nFound 78498 primes in 1.2183 seconds (speedup: 0.98x)\n------------------------------------------------------------\nSUMMARY:\nThreads    Time (s)     Speedup   \n1 (baseline) 1.1930       1.00x     \n4          1.2183       0.98x\n<\/pre>\n<p>Wie Sie sehen, \u00e4ndert sich die Geschwindigkeit bei der Version mit 4 Threads im Vergleich zur Single-Thread-Ausf\u00fchrung nicht wesentlich. Sehen wir uns an, welche Ergebnisse wir erhalten, wenn wir die Free-Threaded-Version 3.13.5t ausf\u00fchren:<\/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=\"\">Python version: 3.13.5 experimental free-threading build (main, Jul 10 2025, 20:36:28) [Clang 17.0.0 (clang-1700.0.13.5)]\nCPU count: 8\nRange size: 1000000\nNumber of chunks: 16\n------------------------------------------------------------\nRunning single-threaded version (baseline)...\nFound 78498 primes in 1.5869 seconds\n------------------------------------------------------------\nRunning multi-threaded version with 4 threads...\nFound 78498 primes in 0.4662 seconds (speedup: 3.40x)\n------------------------------------------------------------\nSUMMARY:\nThreads    Time (s)     Speedup   \n1 (baseline) 1.5869       1.00x     \n4          0.4662       3.40x\n<\/pre>\n<p>Diesmal war die Geschwindigkeit mehr als 3-mal h\u00f6her. Zu beachten ist, dass es in beiden F\u00e4llen einen Overhead f\u00fcr das Multithreading gibt. Daher ist die Geschwindigkeit selbst bei echtem Multithreading mit 4 Threads nicht 4-mal so hoch.<\/p>\n<h2 id=\"conclusion\" class=\"wp-block-heading\">Fazit<\/h2>\n<p>In Teil 2 der Blogreihe \u201eSchnelleres Python\u201c sind wir auf die Gr\u00fcnde f\u00fcr die Verwendung des GIL in Python, die Umgehung der durch den GIL auferlegten Beschr\u00e4nkungen durch Multiprocessing, das Vorgehen zum Entfernen des GIL und die Auswirkungen des Entfernens eingegangen.<\/p>\n<p>Zum Zeitpunkt dieses Blogartikels ist die Free-Threaded-Version von Python noch nicht die Standardversion. Mit der zunehmenden Verbreitung in der Anwendergemeinschaft und der Anpassung der Drittanbieter-Bibliotheken geht die Community jedoch davon aus, dass Free-Threaded-Python in Zukunft zum Standard wird. Es wurde angek\u00fcndigt, dass Python 3.14 eine Free-Threaded-Version enthalten wird, die nicht mehr im experimentellen Stadium, aber immer noch optional sein wird.<\/p>\n<p><a href=\"https:\/\/www.jetbrains.com\/de-de\/pycharm\/\" target=\"_blank\" rel=\"noopener\">PyCharm<\/a> stellt eine klassenf\u00fchrende Python-Unterst\u00fctzung bereit, die das Schreiben von schnellem und korrektem Code erleichtert. Profitieren Sie von der intelligentesten Code-Completion, PEP-8-Pr\u00fcfungen, sinnvollen Refactorings und einer Vielzahl von Inspektionen, die alle Anforderungen bei der Programmierung abdecken. Wie Sie in diesem Blogartikel gesehen haben, erm\u00f6glicht PyCharm benutzerdefinierte Einstellungen f\u00fcr Python-Interpreter und Run-Konfigurationen, sodass Sie mit wenigen Klicks zwischen unterschiedlichen Interpretern wechseln k\u00f6nnen, um die IDE optimal an eine Vielzahl von Python-Projekten anzupassen.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/www.jetbrains.com\/de-de\/pycharm\/\" target=\"\" rel=\"noopener\">PyCharm jetzt herunterladen<\/a><\/div>\n<\/div>\n<p><strong>Autorin des urspr\u00fcnglichen Blogposts<\/strong><\/p>\n\n    <div class=\"about-author \">\n        <div class=\"about-author__box\">\n            <div class=\"row\">\n                <div class=\"about-author__box-img\">\n                    <img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/01\/CheukTingHo-Kimono-e1738750639162-200x200.jpg\" width=\"200\" height=\"200\" alt=\"Cheuk Ting Ho\" loading=\"lazy\"  class=\"avatar avatar-200 wp-user-avatar wp-user-avatar-200 photo avatar-default\">\n                <\/div>\n                <div class=\"about-author__box-text\">\n                                            <h4>Cheuk Ting Ho<\/h4>\n                                                        <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n","protected":false},"author":811,"featured_media":592995,"comment_status":"closed","ping_status":"closed","template":"","categories":[1401,2347],"tags":[8056,5377],"cross-post-tag":[8851],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm\/591263"}],"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=591263"}],"version-history":[{"count":5,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm\/591263\/revisions"}],"predecessor-version":[{"id":650357,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/pycharm\/591263\/revisions\/650357"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media\/592995"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/media?parent=591263"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/categories?post=591263"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/tags?post=591263"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/de\/wp-json\/wp\/v2\/cross-post-tag?post=591263"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}