{"id":593025,"date":"2025-08-18T14:26:03","date_gmt":"2025-08-18T13:26:03","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=593025"},"modified":"2025-10-16T11:02:05","modified_gmt":"2025-10-16T10:02:05","slug":"accelerer-python-lever-le-verrou-gil-de-python","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/fr\/pycharm\/2025\/08\/accelerer-python-lever-le-verrou-gil-de-python\/","title":{"rendered":"Acc\u00e9l\u00e9rer Python : lever le verrou GIL de Python"},"content":{"rendered":"<h2 id=\"what-is-pythons-global-interpreter-lock-gil\" class=\"wp-block-heading\">Qu&#8217;est-ce que le GIL en Python ?<\/h2>\n<p>Le GIL, ou \u00ab Global Interpreter Lock \u00bb, est un terme incontournable dans la communaut\u00e9 Python. C&#8217;est une fonctionnalit\u00e9 bien connue de Python. Mais qu&#8217;est-ce exactement que le GIL ?<\/p>\n<p>Si vous avez d\u00e9j\u00e0 utilis\u00e9 d&#8217;autres langages de programmation, comme Rust par exemple, vous savez probablement ce qu&#8217;est un mutex. C&#8217;est l&#8217;abr\u00e9viation de \u00ab exclusion mutuelle \u00bb. Un mutex garantit que les donn\u00e9es ne sont accessibles que par un seul thread \u00e0 la fois. Cela emp\u00eache plusieurs threads de modifier les donn\u00e9es simultan\u00e9ment. Cela est comparable \u00e0 un \u00ab verrou \u00bb qui limite l&#8217;acc\u00e8s aux donn\u00e9es au seul thread qui en d\u00e9tient la cl\u00e9.<\/p>\n<p>Le GIL est techniquement un mutex. Il ne laisse qu&#8217;un seul thread acc\u00e9der \u00e0 l&#8217;interpr\u00e9teur Python \u00e0 la fois. Je le compare parfois \u00e0 un volant pour Python. Il ne faut jamais laisser plus d&#8217;une personne prendre le volant\u00a0! Ceci dit, un groupe d&#8217;amis partant en road trip va r\u00e9guli\u00e8rement se relayer au volant. C&#8217;est un peu comme transf\u00e9rer l&#8217;acc\u00e8s \u00e0 l&#8217;interpr\u00e9teur \u00e0 un autre thread.<\/p>\n<p>En raison du GIL, Python ne permet pas le v\u00e9ritable multithreading. Cette fonctionnalit\u00e9 est \u00e0 l&#8217;origine de nombreuses discussions au cours de la derni\u00e8re d\u00e9cennie et de nombreuses tentatives ont \u00e9t\u00e9 faites pour acc\u00e9l\u00e9rer Python en supprimant le GIL afin de permettre le multithreading. R\u00e9cemment, avec Python 3.13, une option permettant d&#8217;utiliser Python sans le GIL, parfois appel\u00e9e no-GIL ou \u00e0 threads libres, a \u00e9t\u00e9 mise en place. Cela marque le d\u00e9but d&#8217;une nouvelle \u00e8re de la programmation en Python.<\/p>\n<h2 id=\"why-was-the-gil-there-in-the-first-place\" class=\"wp-block-heading\">\u00c0 quoi servait le GIL \u00e0 l&#8217;origine ?<\/h2>\n<p>Si le GIL est si impopulaire, pourquoi l&#8217;avoir impl\u00e9ment\u00e9 ? En fait, le GIL pr\u00e9sente plusieurs avantages. Dans d&#8217;autres langages de programmation avec un v\u00e9ritable multithreading, la modification des donn\u00e9es par plusieurs threads cr\u00e9e des probl\u00e8mes, car le r\u00e9sultat final d\u00e9pend du thread ou du processus qui se termine en premier. Cela s&#8217;appelle une \u00ab condition de course \u00bb. Des langages tels que Rust sont souvent difficiles \u00e0 apprendre, car les programmeurs doivent utiliser des mutex pour \u00e9viter les conditions de course.<\/p>\n<p>En Python, chaque objet poss\u00e8de un compteur de r\u00e9f\u00e9rences pour suivre combien d&#8217;autres objets y font r\u00e9f\u00e9rence. Si le compteur de r\u00e9f\u00e9rences tombe \u00e0 z\u00e9ro, et dans la mesure o\u00f9 nous savons qu&#8217;il n&#8217;y a pas de condition de course en Python en raison du GIL, nous savons avec certitude que cet objet n&#8217;est plus n\u00e9cessaire et peut \u00eatre collect\u00e9 par le ramasse-miettes.<\/p>\n<p>Lorsque Python est apparu pour la premi\u00e8re fois, en 1991, la plupart des ordinateurs n&#8217;avaient qu&#8217;un seul c\u0153ur et la prise en charge du multithreading \u00e9tait peu demand\u00e9e. La pr\u00e9sence d&#8217;un GIL r\u00e9sout de nombreux probl\u00e8mes d&#8217;impl\u00e9mentation des programmes et facilite \u00e9galement la maintenance du code. Par cons\u00e9quent, un GIL a \u00e9t\u00e9 ajout\u00e9 par Guido van Rossum (le cr\u00e9ateur de Python) en 1992.<\/p>\n<p>Passons \u00e0 2025 : les ordinateurs sont \u00e9quip\u00e9s de processeurs \u00e0 plusieurs c\u0153urs et sont par cons\u00e9quent beaucoup plus puissants. Nous pouvons profiter de cette puissance suppl\u00e9mentaire pour parvenir \u00e0 une v\u00e9ritable concurrence sans supprimer le GIL.<\/p>\n<p>Plus loin dans cet article, nous allons voir en d\u00e9tail le processus de suppression du GIL. Mais pour l&#8217;instant, nous allons regarder comment la concurrence peut v\u00e9ritablement fonctionner avec le GIL en place.<\/p>\n<h2 id=\"multiprocessing-in-python\" class=\"wp-block-heading\">Module multiprocessing de Python<\/h2>\n<p>Avant de regarder en d\u00e9tail le processus de suppression du GIL, nous allons voir comment les d\u00e9veloppeurs Python peuvent parvenir \u00e0 la concurrence en utilisant le module multiprocessing. Ce module de la biblioth\u00e8que standard assure \u00e0 la fois la concurrence locale et distante, ce qui revient \u00e0 contourner le GIL en utilisant des sous-processus \u00e0 la place des threads. De cette fa\u00e7on, le module multiprocessing permet au programmeur d&#8217;exploiter pleinement plusieurs processeurs sur une machine donn\u00e9e.<\/p>\n<p>Cependant, pour r\u00e9aliser le multitraitement, nous devons concevoir notre programme un peu diff\u00e9remment. Prenons l&#8217;exemple suivant d&#8217;utilisation du module multiprocessing de Python.<br \/>Souvenez-vous de notre exemple de restaurant de burger asynchrone dans la <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/06\/concurrency-in-async-await-and-threading\/\">premi\u00e8re partie de cette s\u00e9rie d&#8217;articles de blog<\/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>Nous pouvons utiliser la biblioth\u00e8que multiprocessing pour faire la m\u00eame chose, par exemple :<\/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>Comme vous vous en souvenez peut-\u00eatre, la plupart des m\u00e9thodes de multiprocessing sont tr\u00e8s proches de celles de threading. Pour voir la diff\u00e9rence avec multiprocessing, nous allons explorer un sc\u00e9nario plus complexe :<\/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>Voici le r\u00e9sultat obtenu :<\/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>Notez que multiprocessing pr\u00e9sente des limitations qui conditionnent le code ci-dessus. Examinons ces points un par un.<\/p>\n<p>D&#8217;abord, souvenez-vous que nous utilisions les fonctions make_burger et make_fries pour g\u00e9n\u00e9rer une fonction ayant la bonne valeur order_num\u00a0:<\/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>Nous ne pouvons pas faire la m\u00eame chose avec multiprocessing. Sinon, cela cr\u00e9e une erreur comme celle-ci :<\/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>Cela s&#8217;explique par le fait que multiprocessing utilise <a href=\"https:\/\/docs.python.org\/3\/library\/pickle.html\" target=\"_blank\" rel=\"noopener\">pickle<\/a>, qui ne peut s\u00e9rialiser que les fonctions au niveau sup\u00e9rieur du module en g\u00e9n\u00e9ral. Il s&#8217;agit de l&#8217;une des limitations de multiprocessing.<\/p>\n<p>Ensuite, comme vous pouvez le remarquer dans l&#8217;extrait de code ci-dessus, qui utilise multiprocessing, nous n&#8217;utilisons pas de variables globales pour les donn\u00e9es partag\u00e9es. Par exemple, nous ne pouvons pas utiliser de variables globales pour item_made et order_num. Pour partager les donn\u00e9es entre diff\u00e9rents processus, des objets de classe sp\u00e9ciaux, tels que <em>Queue<\/em> et <em>Value<\/em>, qui proviennent du module multiprocessing, sont utilis\u00e9s et transf\u00e9r\u00e9s aux processus en tant qu&#8217;arguments.<br \/>En g\u00e9n\u00e9ral, le partage des donn\u00e9es et des \u00e9tats entre diff\u00e9rents processus est d\u00e9conseill\u00e9, car cela peut cr\u00e9er de nombreux probl\u00e8mes. Dans notre exemple ci-dessus, nous devons utiliser un Lock pour nous assurer que la valeur de order_num ne peut \u00eatre accessible et incr\u00e9ment\u00e9e que par un seul processus \u00e0 la fois. Sans ce Lock, le num\u00e9ro d&#8217;ordre de l&#8217;\u00e9l\u00e9ment risque de ne pas \u00eatre correct, par exemple :<\/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>Voici comment utiliser un verrou pour \u00e9viter ce probl\u00e8me\u00a0:<\/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>Pour en savoir plus sur l&#8217;utilisation du module multiprocessing de la biblioth\u00e8que standard, vous pouvez consulter la <a href=\"https:\/\/docs.python.org\/3\/library\/multiprocessing.html\" target=\"_blank\" rel=\"noopener\">documentation ici<\/a>.<\/p>\n<h2 id=\"removing-the-gil\" class=\"wp-block-heading\">Suppression du GIL<\/h2>\n<p>La suppression du GIL est en discussion depuis pr\u00e8s d&#8217;une d\u00e9cennie. En 2016, au Python Language Summit, Larry Hastings a pr\u00e9sent\u00e9 ses id\u00e9es sur la mani\u00e8re de pratiquer une \u00ab\u00a0GIL-ectomie\u00a0\u00bb sur l&#8217;interpr\u00e9teur CPython et les progr\u00e8s qu&#8217;il avait r\u00e9alis\u00e9s dans ce sens [1]. Ce fut l&#8217;une des premi\u00e8res tentatives de suppression du GIL de Python. En 2021, Sam Gross a relanc\u00e9 la discussion sur la suppression du GIL [2] et cela a conduit \u00e0 la publication du <a href=\"https:\/\/peps.python.org\/pep-0703\/\" target=\"_blank\" rel=\"noopener\">PEP 703 \u2013 Making the Global Interpreter Lock Optional in CPython<\/a> en 2023, qui vise \u00e0 rendre le GIL facultatif.<\/p>\n<p>Comme nous pouvons le voir, la suppression du GIL n&#8217;est pas une d\u00e9cision h\u00e2tive et a fait l&#8217;objet de nombreux d\u00e9bats au sein de la communaut\u00e9. Comme d\u00e9montr\u00e9 par les exemples ci-dessus de multitraitement (et PEP 703, en lien ci-dessus), lorsque la garantie fournie par le GIL est supprim\u00e9e, les choses se compliquent rapidement.<\/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\">Comptage des r\u00e9f\u00e9rences<\/h3>\n<p>Lorsque le GIL est pr\u00e9sent, le comptage des r\u00e9f\u00e9rences et la collecte par le ramasse-miettes sont plus simples. Si l&#8217;acc\u00e8s aux objets Python est limit\u00e9 \u00e0 un seul thread, nous pouvons compter sur un <a href=\"https:\/\/en.wikipedia.org\/wiki\/Reference_counting\" target=\"_blank\" rel=\"noopener\">comptage des r\u00e9f\u00e9rences<\/a> simple, non atomique, et supprimer l&#8217;objet d\u00e8s que le compteur de r\u00e9f\u00e9rences atteint z\u00e9ro.<\/p>\n<p>La suppression du GIL complique tout. Nous ne pouvons plus utiliser le comptage des r\u00e9f\u00e9rences non atomique, car cela ne garantit pas la <a href=\"https:\/\/en.wikipedia.org\/wiki\/Thread_safety\" target=\"_blank\" rel=\"noopener\">s\u00e9curit\u00e9 des threads<\/a>. Les choses peuvent mal se passer si plusieurs threads augmentent ou r\u00e9duisent la valeur du compteur de r\u00e9f\u00e9rences sur l&#8217;objet Python en m\u00eame temps. Dans l&#8217;id\u00e9al, un comptage des r\u00e9f\u00e9rences atomique serait pr\u00e9f\u00e9rable pour garantir la s\u00e9curit\u00e9 des threads. Toutefois, cette m\u00e9thode impose une surcharge importante et peut se r\u00e9v\u00e9ler peu efficace en pr\u00e9sence d&#8217;un nombre important de threads.<\/p>\n<p>La solution consiste \u00e0 utiliser un comptage des r\u00e9f\u00e9rences biais\u00e9, qui garantit \u00e9galement la s\u00e9curit\u00e9 des threads. L&#8217;id\u00e9e consiste \u00e0 associer chaque objet \u00e0 un thread propri\u00e9taire, \u00e0 savoir celui qui y acc\u00e8de le plus fr\u00e9quemment. Les threads propri\u00e9taires peuvent ex\u00e9cuter le comptage des r\u00e9f\u00e9rences non atomique sur les objets qu&#8217;ils poss\u00e8dent, tandis que les autres threads doivent r\u00e9aliser un comptage des r\u00e9f\u00e9rences atomique sur ces objets. Cette m\u00e9thode est pr\u00e9f\u00e9rable au comptage des r\u00e9f\u00e9rences atomique simple, car la plupart des objets sont uniquement accessibles par un seul thread la plupart du temps. La surcharge d&#8217;ex\u00e9cution peut \u00eatre r\u00e9duite en autorisant le thread propri\u00e9taire \u00e0 ex\u00e9cuter un comptage des r\u00e9f\u00e9rences non atomique.<\/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>De plus, certains objets Python tr\u00e8s utilis\u00e9s, tels que <em>True, False<\/em>, les petits entiers et certaines cha\u00eenes intern\u00e9es, sont rendus immortels. Dans ce cas, \u00ab immortel \u00bb signifie seulement que les objets restent dans le programme pendant toute sa dur\u00e9e de vie, ce qui les dispense du comptage des r\u00e9f\u00e9rences.<\/p>\n<h3 id=\"garbage-collection\" class=\"wp-block-heading\">Ramasse-miettes<\/h3>\n<p>Nous devons \u00e9galement modifier le fonctionnement du <a href=\"https:\/\/en.wikipedia.org\/wiki\/Garbage_collection_(computer_science)\" target=\"_blank\" rel=\"noopener\">ramasse-miettes<\/a>. Au lieu de r\u00e9duire le nombre de r\u00e9f\u00e9rences d\u00e8s qu&#8217;une r\u00e9f\u00e9rence est lib\u00e9r\u00e9e et de retirer l&#8217;objet imm\u00e9diatement lorsque le nombre de r\u00e9f\u00e9rences atteint z\u00e9ro, nous allons utiliser la technique de \u00ab\u00a0comptage des r\u00e9f\u00e9rences diff\u00e9r\u00e9\u00a0\u00bb.<\/p>\n<p>Lorsque le nombre de r\u00e9f\u00e9rences doit \u00eatre r\u00e9duit, l&#8217;objet est stock\u00e9 dans une table, qui sera rev\u00e9rifi\u00e9e pour s&#8217;assurer que la r\u00e9duction du nombre de r\u00e9f\u00e9rences est correcte. Cela \u00e9vite de retirer l&#8217;objet pr\u00e9matur\u00e9ment, alors qu&#8217;il est toujours r\u00e9f\u00e9renc\u00e9, ce qui risque de se produire sans le GIL, dans la mesure o\u00f9 le comptage des r\u00e9f\u00e9rences n&#8217;est pas aussi simple qu&#8217;avec le GIL. Cela complique le processus de collecte du ramasse-miettes, car il peut avoir \u00e0 traverser la <a href=\"https:\/\/en.wikipedia.org\/wiki\/Stack_(abstract_data_type)\" target=\"_blank\" rel=\"noopener\">pile<\/a> de chaque thread pour y ex\u00e9cuter un comptage des r\u00e9f\u00e9rences.<\/p>\n<p>Un autre point important doit \u00eatre pris en compte : le nombre de r\u00e9f\u00e9rences doit rester stable pendant l&#8217;ex\u00e9cution du ramasse-miettes. Si un objet doit \u00eatre supprim\u00e9, mais devient soudain r\u00e9f\u00e9renc\u00e9, cela entra\u00eene des probl\u00e8mes graves. Pour cette raison, le ramasse-miettes devient bloquant (stop the world) pour assurer la s\u00e9curit\u00e9 des threads.<\/p>\n<h3 id=\"memory-allocation\" class=\"wp-block-heading\">Allocation de la m\u00e9moire<\/h3>\n<p>Lorsque le GIL assure la s\u00e9curit\u00e9 du thread, pymalloc, l&#8217;allocateur de m\u00e9moire interne de Python, est utilis\u00e9. Mais sans le GIL, il faut un nouvel allocateur. Sam Gross a propos\u00e9 <a href=\"https:\/\/github.com\/microsoft\/mimalloc\" target=\"_blank\" rel=\"noopener\">mimalloc<\/a> dans son PEP. Il s&#8217;agit d&#8217;un allocateur g\u00e9n\u00e9raliste cr\u00e9\u00e9 par Daan Leijen et g\u00e9r\u00e9 par Microsoft. C&#8217;est un bon choix, car il n&#8217;affecte pas la s\u00e9curit\u00e9 des threads et a de bonnes performances sur les petits objets.<\/p>\n<p>Mimalloc remplit sa pile avec des pages et les pages avec des blocs. Chaque page contient des blocs, et les blocs de chaque page sont de la m\u00eame taille. En ajoutant quelques restrictions d&#8217;acc\u00e8s \u00e0 list et dict, le ramasse-miettes n&#8217;a pas \u00e0 g\u00e9rer une <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linked_list\" target=\"_blank\" rel=\"noopener\">liste cha\u00een\u00e9e<\/a> pour trouver tous les objets et cela permet \u00e9galement d&#8217;acc\u00e9der en lecture aux list et dict sans acqu\u00e9rir le verrou.<\/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>Il faut prendre en compte d&#8217;autres d\u00e9tails concernant la suppression du GIL, mais il est impossible de les couvrir tous dans cet article. Vous pouvez consulter la page <a href=\"https:\/\/peps.python.org\/pep-0703\/\" target=\"_blank\" rel=\"noopener\">PEP 703 \u2013 Making the Global Interpreter Lock Optional in CPython<\/a> pour une pr\u00e9sentation compl\u00e8te.<\/p>\n<h2 id=\"difference-in-performance-with-and-without-the-gil\" class=\"wp-block-heading\">Diff\u00e9rences de performances avec et sans le GIL<\/h2>\n<p>Dans la mesure o\u00f9 Python 3.13 comporte une option \u00e0 threads libres, il est possible de comparer les performances de la version standard de Python 3.13 \u00e0 celle \u00e0 threads libres.<\/p>\n<h3 id=\"install-thread-free-python\" class=\"wp-block-heading\">Installation de Python \u00e0 threads libres<\/h3>\n<p>Nous allons utiliser <a href=\"https:\/\/github.com\/pyenv\/pyenv\" target=\"_blank\" rel=\"noopener\">pyenv<\/a> pour installer les deux versions : la standard (3.13.5) et la version v\u00e9ritablement multithread (3.13.5t).<\/p>\n<p>Par ailleurs, vous pouvez \u00e9galement utiliser les installateurs sur <a href=\"http:\/\/python.org\" target=\"_blank\" rel=\"noopener\">Python.org<\/a>. S\u00e9lectionnez l&#8217;option <em>Customize<\/em> pendant l&#8217;installation et cochez la case de la version multithread de Python (<a href=\"https:\/\/til.simonwillison.net\/python\/trying-free-threaded-python\" target=\"_blank\" rel=\"noopener\">voir l&#8217;exemple dans cet article de blog<\/a>).<\/p>\n<p>Une fois les deux versions install\u00e9es, nous pouvons les ajouter en tant qu&#8217;interpr\u00e9teurs dans un projet PyCharm.<\/p>\n<p>Pour commencer, cliquez sur le nom de votre interpr\u00e9teur Python dans le coin inf\u00e9rieur droit.<\/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>S\u00e9lectionnez <em>Add New Interpreter<\/em> dans le menu, puis <em>Add Local Interpreter<\/em>.<\/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>Choisissez <em>Select existing<\/em>, attendez le chargement du chemin de l&#8217;interpr\u00e9teur (ce qui peut prendre du temps si, comme moi, vous avez beaucoup d&#8217;interpr\u00e9teurs), puis s\u00e9lectionnez le nouvel interpr\u00e9teur que vous venez d&#8217;installer dans le menu d\u00e9roulant <em>Python path<\/em>.<\/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>Cliquez sur <em>OK<\/em> pour l&#8217;ajouter. R\u00e9p\u00e9tez ces \u00e9tapes pour l&#8217;autre interpr\u00e9teur. D\u00e9sormais, lorsque vous cliquez \u00e0 nouveau sur le nom de l&#8217;interpr\u00e9teur dans le coin inf\u00e9rieur droit, vous verrez plusieurs interpr\u00e9teurs Python 3.13, comme dans l&#8217;illustration ci-dessus.<\/p>\n<h3 id=\"testing-with-a-cpu-bounded-process\" class=\"wp-block-heading\">Test de processus li\u00e9 au processeur<\/h3>\n<p>Il faut maintenant cr\u00e9er un script pour tester les diff\u00e9rentes versions. Souvenez-vous, nous avons expliqu\u00e9 dans la <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/06\/concurrency-in-async-await-and-threading\/#cpu-bound-tasks\">premi\u00e8re partie de cette s\u00e9rie d&#8217;articles de blog<\/a> que pour acc\u00e9l\u00e9rer les processus li\u00e9s au processeur, nous devions recourir au multithreading sans restrictions. Pour voir si la suppression du GIL permettra un v\u00e9ritable multithreading et acc\u00e9l\u00e9rera Python, nous pouvons tester un processus li\u00e9 au processeur sur plusieurs threads. Voici le script que j&#8217;ai demand\u00e9 \u00e0 <a href=\"https:\/\/www.jetbrains.com\/junie\/\" target=\"_blank\" rel=\"noopener\">Junie<\/a> de g\u00e9n\u00e9rer (avec quelques retouches personnelles) :<\/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>Pour simplifier l&#8217;ex\u00e9cution du script avec diff\u00e9rents interpr\u00e9teurs Python, nous pouvons ajouter un script d&#8217;ex\u00e9cution personnalis\u00e9 \u00e0 notre projet PyCharm.<\/p>\n<p>En haut, s\u00e9lectionnez <em>Edit Configurations\u2026<\/em> dans le menu d\u00e9roulant pr\u00e8s du bouton <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.png\" width=\"30\" height=\"23\" \/>).<\/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.png\" width=\"391\" height=\"238\" \/><\/p>\n<p>Cliquez sur le bouton + dans le coin sup\u00e9rieur gauche, puis choisissez Python dans le menu d\u00e9roulant <em>Add New Configuration<\/em>.<\/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>Choisissez un nom significatif permettant de savoir quel interpr\u00e9teur est utilis\u00e9, par exemple 3.13.5 et 3.15.3t. S\u00e9lectionnez le bon interpr\u00e9teur et ajoutez ajoutez le nom du script de test de la fa\u00e7on suivante :<\/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>Ajoutez deux configurations, une pour chaque interpr\u00e9teur. Cliquez ensuite sur <em>OK<\/em>.<\/p>\n<p>Maintenant, nous pouvons facilement s\u00e9lectionner et ex\u00e9cuter le script de test avec ou sans le GIL en s\u00e9lectionnant la configuration et en cliquant sur le bouton <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.png\" width=\"30\" height=\"23\" \/>)\u00a0 en haut.<\/p>\n<h3 id=\"comparing-the-results\" class=\"wp-block-heading\">Comparaison des r\u00e9sultats<\/h3>\n<p>Voici le r\u00e9sultat que j&#8217;ai obtenu avec la version standard 3.13.5, incluant le GIL :<\/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>Comme vous pouvez le voir, il n&#8217;y a pas de changement significatif de la vitesse lorsque j&#8217;ex\u00e9cute la version \u00e0 4 threads par rapport \u00e0 la version standard monothread. Voyons maintenant ce qui se produit avec la version v\u00e9ritablement multithread, 3.13.5t :<\/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>Dans ce cas, la vitesse est plus de 3 fois sup\u00e9rieure. Notez que dans les deux cas le multithreading impose une surcharge. Par cons\u00e9quent, m\u00eame avec un v\u00e9ritable multithreading, la vitesse n&#8217;est pas 4 fois sup\u00e9rieure avec 4 threads.<\/p>\n<h2 id=\"conclusion\" class=\"wp-block-heading\">Conclusion<\/h2>\n<p>Dans la deuxi\u00e8me partie de la s\u00e9rie d&#8217;articles de blog \u00ab Acc\u00e9l\u00e9rer Python \u00bb, nous avons vu pourquoi il \u00e9tait important d&#8217;avoir le GIL dans Python, comment contourner la limitation du GIL avec le multitraitement, ainsi que comment supprimer le GIL et les effets de cette suppression.<\/p>\n<p>\u00c0 l&#8217;heure o\u00f9 j&#8217;\u00e9cris cet article, la version v\u00e9ritablement multithread de Python n&#8217;est toujours pas la version par d\u00e9faut. Cependant, avec l&#8217;adoption par la communaut\u00e9 et les biblioth\u00e8ques tierces, la version v\u00e9ritablement multithread de Python devrait devenir la norme \u00e0 l&#8217;avenir. Il a \u00e9t\u00e9 annonc\u00e9 que Python 3.14 inclurait une version v\u00e9ritablement multithread qui serait au-del\u00e0 du stade exp\u00e9rimental, mais resterait facultative.<\/p>\n<p><a href=\"https:\/\/www.jetbrains.com\/fr-fr\/pycharm\/\" target=\"_blank\" rel=\"noopener\">PyCharm<\/a> offre une prise en charge optimale de Python, que ce soit en termes de vitesse ou de pr\u00e9cision. Profitez d&#8217;une saisie semi-automatique avanc\u00e9e, de v\u00e9rifications de conformit\u00e9 PEP 8, de refactorisations intelligentes et de toute une gamme d&#8217;inspections pour r\u00e9pondre \u00e0 tous vos besoins de codage. Comme nous l&#8217;avons vu dans cet article, PyCharm fournit des param\u00e8tres personnalis\u00e9s pour les interpr\u00e9teurs Python et les configurations d&#8217;ex\u00e9cution, afin de s\u00e9lectionner l&#8217;interpr\u00e9teur voulu en quelques clics, ce qui le rend utile pour une large gamme de projets Python.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/www.jetbrains.com\/fr-fr\/pycharm\/\" target=\"\" rel=\"noopener\">T\u00e9l\u00e9chargez PyCharm d\u00e8s maintenant<\/a><\/div>\n<\/div>\n<p><em>Auteur de l\u2019article original en anglais :<\/em><\/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":813,"featured_media":593055,"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\/fr\/wp-json\/wp\/v2\/pycharm\/593025"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/pycharm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/types\/pycharm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/users\/813"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/comments?post=593025"}],"version-history":[{"count":6,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/pycharm\/593025\/revisions"}],"predecessor-version":[{"id":650292,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/pycharm\/593025\/revisions\/650292"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media\/593055"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media?parent=593025"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/categories?post=593025"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/tags?post=593025"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/cross-post-tag?post=593025"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}