{"id":671429,"date":"2025-12-29T10:40:02","date_gmt":"2025-12-29T09:40:02","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=671429"},"modified":"2025-12-29T10:40:05","modified_gmt":"2025-12-29T09:40:05","slug":"etre-plus-rapide-avec-python-concurrence-avec-async-await-et-threading","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/fr\/pycharm\/2025\/12\/etre-plus-rapide-avec-python-concurrence-avec-async-await-et-threading\/","title":{"rendered":"\u00catre plus rapide avec Python\u00a0: concurrence avec async\/await et threading"},"content":{"rendered":"<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-573955\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/PC-social-BlogFeatured-1280x720-2x-14.png\" alt=\"Faster Python Concurrency in async\/await and threading\" width=\"2560\" height=\"1440\" \/><\/figure>\n<p>Si vous programmez avec Python depuis un certain temps, en particulier si vous avez utilis\u00e9 des frameworks et des biblioth\u00e8ques tels que FastAPI et discord.py, alors vous avez probablement utilis\u00e9 <code>async\/await<\/code> ou <code>asyncio<\/code>. Vous avez peut-\u00eatre entendu dire que \u00ab le multithreading en Python n\u2019est pas r\u00e9el \u00bb, ou vous connaissez peut-\u00eatre le fameux (ou tristement c\u00e9l\u00e8bre) GIL de Python. \u00c9tant donn\u00e9 ce scepticisme \u00e0 l&#8217;\u00e9gard du multithreading en Python, vous pourriez vous demander en quoi <code>async\/await<\/code> et le multithreading diff\u00e8rent r\u00e9ellement, notamment dans le contexte de la programmation Python. Si c&#8217;est le cas, cet article est fait pour vous\u00a0!<\/p>\n<h2 id=\"what-is-multithreading\" class=\"wp-block-heading\">Qu&#8217;est-ce que le multithreading\u00a0?<\/h2>\n<p>En programmation, le multithreading d\u00e9signe la capacit\u00e9 d&#8217;un programme \u00e0 ex\u00e9cuter plusieurs t\u00e2ches s\u00e9quentielles (appel\u00e9es threads) simultan\u00e9ment. Ces threads peuvent s&#8217;ex\u00e9cuter sur un seul c\u0153ur de processeur ou sur plusieurs c\u0153urs. Cependant, en Python, le multithreading n&#8217;est g\u00e9r\u00e9 que sur un seul c\u0153ur en raison des limitations du Global Interpreter Lock (GIL). L&#8217;exception est Python nogil (\u00e9galement appel\u00e9 sans thread), qui supprime le GIL et sera abord\u00e9 dans la deuxi\u00e8me partie de cette s\u00e9rie d&#8217;articles. Pour cet article, nous supposerons que le GIL est toujours pr\u00e9sent.<\/p>\n<h2 id=\"what-is-concurrency\" class=\"wp-block-heading\">Qu&#8217;est-ce que la concurrence\u00a0?<\/h2>\n<p>En programmation, la concurrence signifie que l&#8217;ordinateur fait plusieurs choses \u00e0 la fois, ou semble faire plusieurs choses \u00e0 la fois, m\u00eame si les diff\u00e9rentes t\u00e2ches sont ex\u00e9cut\u00e9es sur un seul processeur. En g\u00e9rant les ressources et les interactions entre les diff\u00e9rentes parties d&#8217;un programme, diff\u00e9rentes t\u00e2ches sont autoris\u00e9es \u00e0 progresser ind\u00e9pendamment et sur des plages de temps qui se chevauchent.<\/p>\n<h3 id=\"both-asyncio-and-threading-appear-concurrent-in-python\" class=\"wp-block-heading\"><code>asyncio<\/code> et <code>threading<\/code> pr\u00e9sentent toutes deux une capacit\u00e9 de concurrence en Python<\/h3>\n<p>En gros, les biblioth\u00e8ques Python <code>asyncio<\/code> et <code>threading<\/code> prennent en charge la concurrence. Cependant, vos processeurs ne font pas plusieurs choses exactement en m\u00eame temps. On dirait seulement que c&#8217;est le cas.<\/p>\n<p>Imaginez que vous organisez un d\u00eener avec plusieurs plats pour des invit\u00e9s. Certains plats prennent du temps \u00e0 pr\u00e9parer, par exemple, la tarte qui doit \u00eatre cuite au four ou la soupe qui doit mijoter sur la cuisini\u00e8re. En attendant qu&#8217;ils soient pr\u00eats, nous ne nous contentons pas d&#8217;attendre. Nous faisons autre chose entre-temps. C&#8217;est un peu pareil pour la concurrence en Python. Parfois, votre processus Python attend que quelque chose soit fait. Par exemple, certains processus d&#8217;entr\u00e9e\/sortie (I\/O) sont g\u00e9r\u00e9s par le syst\u00e8me d&#8217;exploitation, et pendant ce temps, le processus Python est simplement en attente. Dans ce cas, nous pouvons utiliser async pour laisser un autre processus Python s&#8217;ex\u00e9cuter pendant cette attente.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574536\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-23.png\" alt=\"Multithreading vs asyncio en Python\" width=\"1600\" height=\"896\" \/><\/figure>\n<h3 id=\"the-difference-is-who-is-in-charge\" class=\"wp-block-heading\">La diff\u00e9rence est qui a le contr\u00f4le<\/h3>\n<p>Si <code>asyncio<\/code> et <code>threading<\/code> permettent toutes les deux la concurrence, quelle est la diff\u00e9rence entre elles\u00a0? Eh bien, la principale diff\u00e9rence porte sur qui d\u00e9cide quel processus s&#8217;ex\u00e9cute et quand. Pour <code>async\/await<\/code>, l&#8217;approche est parfois appel\u00e9e concurrence coop\u00e9rative. Une coroutine ou un objet Future peut passer le contr\u00f4le \u00e0 une autre coroutine ou un autre Future permettant ainsi \u00e0 un autre processus de s&#8217;ex\u00e9cuter. D&#8217;un autre c\u00f4t\u00e9, avec <code>threading<\/code>, c&#8217;est le gestionnaire du syst\u00e8me d&#8217;exploitation qui d\u00e9cide quel processus s&#8217;ex\u00e9cute.<\/p>\n<p>La concurrence coop\u00e9rative est comme une r\u00e9union dans laquelle un micro passe dans l&#8217;assembl\u00e9e pour que les gens s&#8217;expriment. La personne qui a le micro peut parler, et quand elle a termin\u00e9 ou n&#8217;a plus rien d&#8217;autre \u00e0 dire, elle passe le micro \u00e0 la personne suivante. En revanche, le multithreading est une r\u00e9union dans laquelle un pr\u00e9sident d\u00e9termine qui a la parole \u00e0 un moment donn\u00e9.\u00a0<\/p>\n<h2 id=\"writing-concurrent-code-in-python\" class=\"wp-block-heading\">\u00c9crire du code concurrent en Python<\/h2>\n<p>Voyons comment fonctionne la concurrence en Python en \u00e9crivant un exemple de code. Nous allons cr\u00e9er une simulation de restauration rapide en utilisant <code>asyncio<\/code> et <code>threading<\/code>.<\/p>\n<h3 id=\"how-async-await-works-in-python\" class=\"wp-block-heading\">Comment fonctionne <code>async\/await<\/code> en Python\u00a0?<\/h3>\n<p>Le paquet <code>asyncio<\/code> a \u00e9t\u00e9 introduit dans Python 3.4, tandis que les mots-cl\u00e9s <code>async<\/code> et <code>await<\/code> ont \u00e9t\u00e9 introduits dans Python\u00a03.5. L&#8217;un des principaux \u00e9l\u00e9ments rendant <code>async\/await<\/code> possible est l&#8217;utilisation de coroutines. Les coroutines en Python sont en fait des g\u00e9n\u00e9rateurs repens\u00e9s afin de pouvoir faire une pause et de revenir \u00e0 la fonction principale.<\/p>\n<p>Imaginez maintenant un restaurant de burgers dans lequel un seul membre du personnel travaille. Les commandes sont pr\u00e9par\u00e9es selon la m\u00e9thode premier arriv\u00e9, premier servi, et aucune op\u00e9ration asynchrone ne peut \u00eatre effectu\u00e9e\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=\"\">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>Cela prendra un certain temps\u00a0:<\/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>Maintenant, imaginez que le restaurant embauche plus de personnel, afin de pouvoir effectuer des t\u00e2ches simultan\u00e9ment\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=\"\">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>Nous voyons la diff\u00e9rence entre les deux\u00a0:<\/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>En utilisant les fonctions fournies par <code>asyncio<\/code>, comme <code>run<\/code> et <code>gather<\/code>, et les mots-cl\u00e9s <code>async<\/code> et <code>await<\/code>, nous avons cr\u00e9\u00e9 des coroutines capables de faire des burgers simultan\u00e9ment.<\/p>\n<p>Allons maintenant plus loin pour cr\u00e9er une simulation plus compliqu\u00e9e. Imaginez que nous n&#8217;avons que deux employ\u00e9s et que nous ne pouvons faire que deux burgers \u00e0 la fois.<\/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>Ici, nous utiliserons une file d&#8217;attente pour stocker les t\u00e2ches, et le personnel les r\u00e9cup\u00e9rera.<\/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>Dans cet exemple, nous utilisons <code>asyncio.Queue<\/code> pour stocker les t\u00e2ches, mais ce sera plus utile si nous avons plusieurs types de t\u00e2ches, comme dans l&#8217;exemple suivant.<\/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>Dans cet exemple, il y a plusieurs t\u00e2ches, notamment la pr\u00e9paration des frites, qui prend moins de temps, et la prise de commandes, qui implique d&#8217;obtenir des informations de l&#8217;utilisateur.\u00a0<\/p>\n<p>Notez que le programme s&#8217;arr\u00eate pour attendre l&#8217;entr\u00e9e de l&#8217;utilisateur, et m\u00eame les autres membres du personnel (qui ne prennent pas la commande) cessent de travailler en arri\u00e8re-plan. Ceci est d\u00fb au fait que la fonction <code>input<\/code> n&#8217;est pas asynchrone et n&#8217;est donc pas attendue. N&#8217;oubliez pas que le contr\u00f4le dans le code asynchrone n&#8217;est lib\u00e9r\u00e9 que lorsqu&#8217;il est attendu. Pour r\u00e9soudre ce probl\u00e8me, nous pouvons remplacer\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=\"\">input(\"Number of burgers:\")<\/pre>\n<p>Par\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=\"\">await asyncio.to_thread(input, \"Number of burgers:\")<\/pre>\n<p>Et nous faisons de m\u00eame pour les frites\u00a0; regardez le code ci-dessous. Notez que maintenant le programme fonctionne dans une boucle infinie. Si nous devons l&#8217;arr\u00eater, nous pouvons d\u00e9lib\u00e9r\u00e9ment interrompre le programme avec une entr\u00e9e invalide.<\/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>En utilisant <code>asyncio.to_thread<\/code>, nous avons plac\u00e9 la fonction <code>input<\/code> dans un thread s\u00e9par\u00e9 (<a href=\"https:\/\/docs.python.org\/3\/library\/asyncio-task.html#running-in-threads\" target=\"_blank\" rel=\"noopener\">voir cette r\u00e9f\u00e9rence<\/a>). Notez toutefois que cette astuce ne d\u00e9bloque les t\u00e2ches associ\u00e9es aux I\/O que si le GIL Python est pr\u00e9sent.<\/p>\n<p>Si vous ex\u00e9cutez le code ci-dessus, vous pouvez \u00e9galement voir que les I\/O standard du terminal sont m\u00e9lang\u00e9es. Les I\/O de l&#8217;utilisateur et l&#8217;enregistrement de ce qui se passe doivent \u00eatre s\u00e9par\u00e9s. Nous pouvons enregistrer ces informations dans un fichier journal pour les consulter plus tard.\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=\"\">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>Dans ce dernier bloc de code, nous avons consign\u00e9 les informations de simulation dans <code>pyburger.log<\/code> et r\u00e9serv\u00e9 le terminal pour les messages destin\u00e9s aux clients. Nous d\u00e9tectons \u00e9galement les entr\u00e9es non valides pendant le processus de commande et basculons un indicateur <code>closing<\/code> sur <code>True<\/code> si l&#8217;entr\u00e9e n&#8217;est pas valide, en supposant que l&#8217;utilisateur souhaite quitter. Une fois que l&#8217;indicateur <code>closing<\/code> est d\u00e9fini sur <code>True<\/code>, l&#8217;employ\u00e9 passe \u00e0 <code>return<\/code>, ce qui met fin \u00e0 la boucle <code>while<\/code> infinie de la coroutine.<\/p>\n<h3 id=\"how-does-threading-work-in-python\" class=\"wp-block-heading\">Comment fonctionne le <code>threading<\/code> en Python\u00a0?<\/h3>\n<p>Dans l&#8217;exemple ci-dessus, nous pla\u00e7ons une t\u00e2che associ\u00e9e aux I\/O dans un autre thread. Vous vous demandez peut-\u00eatre si nous pouvons placer toutes les t\u00e2ches dans des threads s\u00e9par\u00e9s et les laisser s&#8217;ex\u00e9cuter simultan\u00e9ment. Essayons d&#8217;utiliser <code>threading<\/code> au lieu d&#8217;<code>asyncio<\/code>.<\/p>\n<p>Prenez le code que nous avons, comme indiqu\u00e9 ci-dessous, o\u00f9 nous cr\u00e9ons des burgers simultan\u00e9ment sans avoir mis en place aucune limitation\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=\"\">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>Dans la premi\u00e8re boucle <code>for<\/code> dans <code>main<\/code>, les t\u00e2ches sont cr\u00e9\u00e9es dans diff\u00e9rents threads et peuvent d\u00e9marrer rapidement. La deuxi\u00e8me boucle <code>for<\/code> s&#8217;assure que tous les burgers sont faits avant que le programme ne passe \u00e0 la suite (c&#8217;est-\u00e0-dire avant qu&#8217;il ne revienne \u00e0 <code>main<\/code>).<\/p>\n<p>C&#8217;est plus compliqu\u00e9 lorsque nous n&#8217;avons que deux employ\u00e9s. Chaque membre du personnel est repr\u00e9sent\u00e9 par un thread, et ils prennent des t\u00e2ches \u00e0 partir d&#8217;une liste normale o\u00f9 elles sont toutes stock\u00e9es.<\/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>Lorsque vous ex\u00e9cutez le code ci-dessus, une erreur peut se produire dans l&#8217;un des threads, indiquant qu&#8217;il essaie d&#8217;obtenir une t\u00e2che \u00e0 partir d&#8217;une liste vide. Vous vous demandez peut-\u00eatre pourquoi c&#8217;est le cas, puisque nous avons une condition dans la boucle <code>while<\/code> qui fait qu&#8217;elle continue uniquement si la file d&#8217;attente <code>task_queue<\/code> n&#8217;est pas vide. Mais nous obtenons quand m\u00eame une erreur, car nous avons rencontr\u00e9 des conditions de concurrence.<\/p>\n<h3 id=\"race-conditions\" class=\"wp-block-heading\">Conditions de concurrence<\/h3>\n<p>Des conditions de concurrence (race conditions) peuvent se produire lorsque plusieurs threads tentent d&#8217;acc\u00e9der \u00e0 la m\u00eame ressource ou aux m\u00eames donn\u00e9es en m\u00eame temps et causent des probl\u00e8mes dans le syst\u00e8me. Le moment et l&#8217;ordre d&#8217;acc\u00e8s \u00e0 la ressource sont importants pour la logique du programme, et une chronologie impr\u00e9visible ou l&#8217;entrelacement de plusieurs threads acc\u00e9dant \u00e0 des donn\u00e9es partag\u00e9es pour les modifier peuvent provoquer des erreurs.<\/p>\n<p>Pour r\u00e9soudre la condition de concurrence dans notre programme, nous allons d\u00e9ployer un verrou sur la file d&#8217;attente <code>task_queue<\/code>\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=\"\">queue_lock = threading.Lock()<\/pre>\n<p>Pour que cela fonctionne, nous devons nous assurer de disposer des autorisations d&#8217;acc\u00e8s n\u00e9cessaires pour v\u00e9rifier la longueur de la file d&#8217;attente et r\u00e9cup\u00e9rer les t\u00e2ches qui en sont extraites. Tant que nous avons les droits, les autres threads ne peuvent pas acc\u00e9der \u00e0 la file d&#8217;attente\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 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>Si vous comparez les deux extraits de code utilisant <code>asyncio<\/code> et <code>threading<\/code>, leurs r\u00e9sultats devraient \u00eatre similaires. Vous vous demandez peut-\u00eatre lequel est le meilleur et pourquoi choisir l&#8217;un plut\u00f4t que l&#8217;autre.<\/p>\n<p>En pratique, l&#8217;\u00e9criture de code <code>asyncio<\/code> est plus facile que le multithreading, car nous n&#8217;avons pas \u00e0 nous occuper nous-m\u00eames des conditions de concurrence potentielles et des blocages. Par d\u00e9faut, les contr\u00f4les sont transmis entre les coroutines, donc aucun verrou n&#8217;est n\u00e9cessaire. Les threads Python ont toutefois la possibilit\u00e9 de fonctionner en parall\u00e8le, mais pas la plupart du temps si le GIL est en place. Nous pourrons revenir sur ce point lorsque nous parlerons de nogil (sans thread) Python dans le prochain article.<\/p>\n<h2 id=\"benefiting-from-concurrency\" class=\"wp-block-heading\">B\u00e9n\u00e9ficier de la concurrence<\/h2>\n<p>Pourquoi utiliser la concurrence en programmation\u00a0? Principalement pour une raison\u00a0: la vitesse. Comme nous l&#8217;avons illustr\u00e9 ci-dessus, les t\u00e2ches s&#8217;accomplissent plus rapidement si nous pouvons r\u00e9duire le temps d&#8217;attente. Plusieurs types d&#8217;attente existent en informatique, et pour chacun d&#8217;entre eux, nous avons tendance \u00e0 utiliser des m\u00e9thodes diff\u00e9rentes pour gagner du temps.<\/p>\n<h3 id=\"i-o-bound-tasks\" class=\"wp-block-heading\">T\u00e2ches associ\u00e9es aux I\/O<\/h3>\n<p>Une t\u00e2che ou un programme est consid\u00e9r\u00e9 comme associ\u00e9 aux entr\u00e9es\/sorties (I\/O) lorsque sa vitesse d&#8217;ex\u00e9cution est principalement limit\u00e9e par la vitesse des op\u00e9rations d&#8217;I\/O (par exemple la lecture \u00e0 partir d&#8217;un fichier ou d&#8217;un r\u00e9seau, ou l&#8217;attente d&#8217;une entr\u00e9e utilisateur). Les op\u00e9rations d&#8217;I\/O sont g\u00e9n\u00e9ralement plus lentes que les autres op\u00e9rations du processeur et, par cons\u00e9quent, les t\u00e2ches qui en impliquent un grand nombre peuvent prendre beaucoup plus de temps. Parmi ces t\u00e2ches, on peut citer la lecture de donn\u00e9es \u00e0 partir d&#8217;une base de donn\u00e9es, la gestion de requ\u00eates web ou le recours \u00e0 des fichiers volumineux.<\/p>\n<p>L&#8217;utilisation de la concurrence <code>async\/await<\/code> peut aider \u00e0 optimiser le temps d&#8217;attente pendant les t\u00e2ches li\u00e9es aux I\/O en d\u00e9bloquant la s\u00e9quence de traitement pour que d&#8217;autres t\u00e2ches soient prises en charge pendant l&#8217;attente.<\/p>\n<p>La concurrence <code>async\/await<\/code> est b\u00e9n\u00e9fique dans de nombreuses applications Python, telles que les applications web qui impliquent beaucoup de communication avec des bases de donn\u00e9es et la gestion de requ\u00eates web. Les interfaces utilisateur graphiques (GUI) peuvent \u00e9galement b\u00e9n\u00e9ficier de la simultan\u00e9it\u00e9 <code>async\/await<\/code> en permettant d&#8217;effectuer des t\u00e2ches en arri\u00e8re-plan pendant que l&#8217;utilisateur interagit avec l&#8217;application.<\/p>\n<h3 id=\"cpu-bound-tasks\" class=\"wp-block-heading\">T\u00e2ches associ\u00e9es au processeur<\/h3>\n<p>Une t\u00e2che ou un programme est consid\u00e9r\u00e9 comme li\u00e9 au processeur lorsque sa vitesse d&#8217;ex\u00e9cution est principalement limit\u00e9e par la vitesse du CPU. Parmi les exemples typiques, on trouve le traitement d&#8217;images ou de vid\u00e9os, comme le redimensionnement ou l&#8217;\u00e9dition, et les calculs math\u00e9matiques complexes, tels que la multiplication matricielle ou l&#8217;entra\u00eenement de mod\u00e8les de machine learning.<\/p>\n<p>Contrairement aux t\u00e2ches associ\u00e9es aux I\/O, les t\u00e2ches li\u00e9es au processeur peuvent rarement \u00eatre optimis\u00e9es \u00e0 l&#8217;aide de la concurrence <code>async\/await<\/code>, car le processeur est d\u00e9j\u00e0 occup\u00e9 \u00e0 travailler sur les t\u00e2ches. Si vous avez plusieurs processeurs dans votre machine, ou si vous pouvez d\u00e9charger certaines de ces t\u00e2ches sur un ou plusieurs GPU, les t\u00e2ches li\u00e9es au processeur peuvent \u00eatre termin\u00e9es plus rapidement gr\u00e2ce \u00e0 la cr\u00e9ation de threads suppl\u00e9mentaires pour effectuer un multitraitement. Le multitraitement peut optimiser l&#8217;utilisation de ces CPU et GPU, ce qui explique \u00e9galement pourquoi de nombreux mod\u00e8les de machine learning et d&#8217;IA sont aujourd&#8217;hui entra\u00een\u00e9s sur plusieurs GPU.<\/p>\n<p>Cela reste toutefois difficile \u00e0 r\u00e9aliser avec du code Python pur, car Python lui-m\u00eame est con\u00e7u pour fournir des couches abstraites afin que les utilisateurs n&#8217;aient pas \u00e0 contr\u00f4ler les processus de calcul de niveau inf\u00e9rieur. De plus, le GIL de Python limite le partage des ressources Python entre plusieurs threads sur votre ordinateur. R\u00e9cemment, Python\u00a03.13 a permis de supprimer le GIL, pour permettre un v\u00e9ritable multithreading. Nous parlerons du GIL et de la possibilit\u00e9 de s&#8217;en passer dans un prochain article de blog.<\/p>\n<p>Parfois, aucune des m\u00e9thodes que nous avons mentionn\u00e9es ci-dessus n&#8217;est capable d&#8217;acc\u00e9l\u00e9rer suffisamment les t\u00e2ches li\u00e9es au CPU. Dans ces cas, il peut \u00eatre n\u00e9cessaire de diviser les t\u00e2ches li\u00e9es au processeur en plus petites t\u00e2ches afin de pouvoir les ex\u00e9cuter simultan\u00e9ment sur plusieurs threads, plusieurs processeurs ou m\u00eame plusieurs machines. Il s&#8217;agit du traitement parall\u00e8le, et il peut \u00eatre n\u00e9cessaire de r\u00e9\u00e9crire compl\u00e8tement votre code pour l&#8217;impl\u00e9menter. En Python, le paquet <code>multiprocessing<\/code> offre une concurrence locale et distante, qui peut \u00eatre utilis\u00e9e pour contourner la limitation du GIL. Nous en examinerons \u00e9galement quelques exemples dans un prochain article de blog.<\/p>\n<h2 id=\"debugging-concurrent-code-in-py-charm\" class=\"wp-block-heading\">D\u00e9bogage du code concurrent dans PyCharm<\/h2>\n<p>Le d\u00e9bogage de code asynchrone ou concurrent peut s&#8217;av\u00e9rer compliqu\u00e9, car le programme n&#8217;est ps ex\u00e9cut\u00e9 de mani\u00e8re s\u00e9quentielle, ce qui rend difficile de voir o\u00f9 et quand le code est ex\u00e9cut\u00e9. De nombreux d\u00e9veloppeurs utilisent <code>print<\/code> pour aider \u00e0 tracer le flux du code, mais cette approche n&#8217;est pas recommand\u00e9e, car elle est tr\u00e8s maladroite, et l&#8217;utiliser pour \u00e9tudier un programme complexe, comme un programme concurrent, n&#8217;est pas facile. De plus, remettre le code en ordre apr\u00e8s cela est fastidieux.<\/p>\n<p>De nombreux IDE fournissent des d\u00e9bogueurs, qui sont parfaits pour inspecter les variables et le flux du programme. Les d\u00e9bogueurs fournissent \u00e9galement une trace de pile claire sur plusieurs threads. Voyons comment suivre les files d&#8217;attente <code>task_queue<\/code> de notre exemple de simulation de restaurant dans <a href=\"https:\/\/www.jetbrains.com\/pycharm\/\" target=\"_blank\" rel=\"noopener\" data-type=\"link\" data-id=\"https:\/\/www.jetbrains.com\/pycharm\/data-science\/\">PyCharm<\/a>.<\/p>\n<p>Tout d&#8217;abord, nous allons placer quelques points d&#8217;arr\u00eat dans notre code. Pour ce faire, cliquez sur le num\u00e9ro de la ligne sur laquelle vous souhaitez que le d\u00e9bogueur s&#8217;arr\u00eate. Le num\u00e9ro de la ligne se transforme en point rouge, indiquant qu&#8217;un point d&#8217;arr\u00eat y est d\u00e9fini. Nous allons placer des points d&#8217;arr\u00eat aux lignes\u00a023, 27 et 65, o\u00f9 la file d&#8217;attente des t\u00e2ches <code>task_queue<\/code> est modifi\u00e9e dans plusieurs threads.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574369\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-8.png\" alt=\"\" width=\"1600\" height=\"720\" \/><\/figure>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574380\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-9.png\" alt=\"\" width=\"1600\" height=\"554\" \/><\/figure>\n<p>Nous pouvons ensuite ex\u00e9cuter le programme en mode d\u00e9bogage en cliquant sur la petite ic\u00f4ne d&#8217;insecte en haut \u00e0 droite.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574391\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-10.png\" alt=\"\" width=\"860\" height=\"258\" \/><\/figure>\n<p>Un clic sur l&#8217;ic\u00f4ne ouvre la fen\u00eatre <em>Debug<\/em>. Le programme s&#8217;ex\u00e9cute alors jusqu&#8217;\u00e0 atteindre le premier point d&#8217;arr\u00eat mis en \u00e9vidence dans le code.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574402\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-11.png\" alt=\"\" width=\"1600\" height=\"1002\" \/><\/figure>\n<p>Ici, nous voyons que le thread <code>John<\/code> essaie de reprendre la t\u00e2che, et la ligne\u00a065 est mise en \u00e9vidence. \u00c0 ce stade, la ligne en surbrillance n&#8217;a pas encore \u00e9t\u00e9 ex\u00e9cut\u00e9e. Cela nous permet d&#8217;inspecter les variables avant d&#8217;entrer dans le point d&#8217;arr\u00eat.<\/p>\n<p>Voyons ce que contient la file d&#8217;attente <code>task_queue<\/code>. Pour cela, vous pouvez simplement commencer \u00e0 taper dans la fen\u00eatre <em>Debug<\/em>, comme indiqu\u00e9 ci-dessous.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574414\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-12.png\" alt=\"\" width=\"1600\" height=\"468\" \/><\/figure>\n<p>S\u00e9lectionnez ou tapez \u00ab\u00a0task_queue\u00a0\u00bb, puis appuyez sur <em>Entr\u00e9e<\/em>. Vous verrez que la t\u00e2che <code>take_order<\/code> se trouve dans la file d&#8217;attente.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574425\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-13.png\" alt=\"\" width=\"1600\" height=\"527\" \/><\/figure>\n<p>Maintenant, ex\u00e9cutons le point d&#8217;arr\u00eat en cliquant sur le bouton <em>Step in<\/em>, comme indiqu\u00e9 ci-dessous.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574436\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-14.png\" alt=\"\" width=\"1274\" height=\"488\" \/><\/figure>\n<p>Apr\u00e8s avoir cliqu\u00e9 dessus et inspect\u00e9 la fen\u00eatre <em>Special Variables<\/em> qui s&#8217;affiche alors, nous voyons que la variable task est maintenant <code>take_order<\/code> dans le thread <code>John<\/code>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574447\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-15.png\" alt=\"\" width=\"1600\" height=\"522\" \/><\/figure>\n<p>Lorsque nous interrogeons \u00e0 nouveau la <code>task_queue<\/code>, nous constatons que la liste est vide.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574458\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-16.png\" alt=\"\" width=\"1600\" height=\"451\" \/><\/figure>\n<p>Cliquons maintenant sur le bouton <em>Resume Program<\/em> et laissons le programme s&#8217;ex\u00e9cuter.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574469\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-17.png\" alt=\"\" width=\"994\" height=\"610\" \/><\/figure>\n<p>Lorsque le programme atteint la partie d&#8217;entr\u00e9e par l&#8217;utilisateur, PyCharm nous am\u00e8ne \u00e0 la fen\u00eatre <em>Console<\/em> afin que nous puissions fournir l&#8217;entr\u00e9e. Supposons que nous voulons deux burgers. Tapez \u00ab\u00a02\u00a0\u00bb et appuyez sur <em>Entr\u00e9e<\/em>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574480\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-18.png\" alt=\"\" width=\"1288\" height=\"616\" \/><\/figure>\n<p>Nous atteignons maintenant le deuxi\u00e8me point d&#8217;arr\u00eat. Si nous cliquons sur <em>Threads &amp; Variables<\/em> pour revenir \u00e0 cette fen\u00eatre, nous verrons que <code>burger_num<\/code> est \u00e9gal \u00e0 deux, comme nous l&#8217;avons demand\u00e9.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574491\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-19.png\" alt=\"\" width=\"1600\" height=\"1002\" \/><\/figure>\n<p>Entrons maintenant dans le point d&#8217;arr\u00eat et inspectons la <code>task_queue<\/code>, comme pr\u00e9c\u00e9demment. Nous voyons qu&#8217;une t\u00e2che <code>make_burger<\/code> a \u00e9t\u00e9 ajout\u00e9e.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574502\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-20.png\" alt=\"\" width=\"1600\" height=\"405\" \/><\/figure>\n<p>Nous laissons le programme s&#8217;ex\u00e9cuter \u00e0 nouveau, et si nous avan\u00e7ons pas \u00e0 pas dans le point d&#8217;arr\u00eat lorsqu&#8217;il s&#8217;arr\u00eate, nous voyons que <code>Jane<\/code> reprend la t\u00e2che.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574513\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-21.png\" alt=\"\" width=\"1600\" height=\"1002\" \/><\/figure>\n<p>Vous pouvez inspecter le reste du code vous-m\u00eame. Lorsque vous avez termin\u00e9, appuyez simplement sur le bouton rouge <em>Stop<\/em> en haut de la fen\u00eatre.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-574524\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/06\/image-22.png\" alt=\"\" width=\"848\" height=\"244\" \/><\/figure>\n<p>Avec le d\u00e9bogueur de PyCharm, vous pouvez tr\u00e8s facilement suivre l&#8217;ex\u00e9cution de votre programme sur diff\u00e9rents threads et inspecter les diff\u00e9rentes variables.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<h2 id=\"conclusion\" class=\"wp-block-heading\">Conclusion<\/h2>\n<p>Nous avons vu dans cet article les bases de la concurrence en Python, et j&#8217;esp\u00e8re que vous serez en mesure de les ma\u00eetriser avec de la pratique. Dans le prochain article, nous examinerons le GIL Python, son r\u00f4le et ce qui change en son absence.<\/p>\n<p>PyCharm fournit des outils puissants pour travailler avec du code Python concurrent. Comme nous l&#8217;avons vu, le d\u00e9bogueur permet inspection \u00e9tape par \u00e9tape du code asynchrone et multithread, ce qui vous aide \u00e0 suivre le flux d&#8217;ex\u00e9cution, \u00e0 surveiller les ressources partag\u00e9es et \u00e0 d\u00e9tecter les probl\u00e8mes. Avec des points d&#8217;arr\u00eat intuitifs, des vues de variables en temps r\u00e9el, une int\u00e9gration transparente de la console pour la saisie utilisateur et une prise en charge robuste de la journalisation, PyCharm facilite l&#8217;\u00e9criture, le test et le d\u00e9bogage des applications de fa\u00e7on claire et assur\u00e9e.<\/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\n\n<p><em>Auteur de l&#8217;article original en anglais<\/em> :<\/p>\n\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":573955,"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\/fr\/wp-json\/wp\/v2\/pycharm\/671429"}],"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=671429"}],"version-history":[{"count":5,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/pycharm\/671429\/revisions"}],"predecessor-version":[{"id":671435,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/pycharm\/671429\/revisions\/671435"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media\/573955"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media?parent=671429"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/categories?post=671429"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/tags?post=671429"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/cross-post-tag?post=671429"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}