{"id":591088,"date":"2025-06-27T01:20:00","date_gmt":"2025-06-27T00:20:00","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=591088"},"modified":"2025-08-12T01:22:24","modified_gmt":"2025-08-12T00:22:24","slug":"python-mais-rapido-concorrencia-com-async-await-e-threading","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/pt-br\/pycharm\/2025\/06\/python-mais-rapido-concorrencia-com-async-await-e-threading\/","title":{"rendered":"Python mais r\u00e1pido: concorr\u00eancia com async\/await e threading"},"content":{"rendered":"<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-591093\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/08\/PC-social-BlogFeatured-1280x720-2x-1.png\" alt=\"\" width=\"2560\" height=\"1440\" \/><\/figure>\n<p>Se voc\u00ea j\u00e1 programa em Python h\u00e1 algum tempo, especialmente se utiliza frameworks e bibliotecas como FastAPI e discord.py, provavelmente j\u00e1 utilizou <code>async\/await<\/code> ou <code>asyncio<\/code>. Voc\u00ea pode ter ouvido afirma\u00e7\u00f5es como \u201cmultithreading em Python n\u00e3o \u00e9 real\u201d e tamb\u00e9m talvez conhe\u00e7a o famoso (ou indesej\u00e1vel) GIL em Python. Considerando a inexist\u00eancia de multithreading em Python, voc\u00ea pode estar se perguntando qual \u00e9 a diferen\u00e7a entre <code>async\/await<\/code> e multithreading, especialmente na programa\u00e7\u00e3o em Python. Se sim, este \u00e9 o artigo ideal para voc\u00ea!<\/p>\n<h2 id=\"what-is-multithreading\" class=\"wp-block-heading\">O que \u00e9 multithreading?<\/h2>\n<p>Em programa\u00e7\u00e3o, multithreading refere-se \u00e0 capacidade de um programa executar v\u00e1rias tarefas sequenciais (chamadas threads) simultaneamente. Esses threads podem ser executados em um \u00fanico n\u00facleo do processador ou em v\u00e1rios n\u00facleos. No entanto, devido \u00e0 limita\u00e7\u00e3o do Global Interpreter Lock (GIL), o multithreading em Python \u00e9 processado apenas em um \u00fanico n\u00facleo. A exce\u00e7\u00e3o \u00e9 o Python nogil (tamb\u00e9m chamado de thread-free), que remove o GIL e ser\u00e1 abordado na parte 2 desta s\u00e9rie. Para esta publica\u00e7\u00e3o no blog, assumiremos que o GIL est\u00e1 sempre presente.<\/p>\n<h2 id=\"what-is-concurrency\" class=\"wp-block-heading\">O que \u00e9 concorr\u00eancia?<\/h2>\n<p>Concorr\u00eancia em programa\u00e7\u00e3o significa que o computador est\u00e1 realizando mais de uma tarefa ao mesmo tempo, ou parece estar realizando mais de uma tarefa ao mesmo tempo, mesmo que as diferentes tarefas sejam executadas em um \u00fanico processador. Ao gerenciar recursos e intera\u00e7\u00f5es entre diferentes partes de um programa, diferentes tarefas podem progredir de forma independente e em intervalos de tempo sobrepostos.<\/p>\n<h3 id=\"both-asyncio-and-threading-appear-concurrent-in-python\" class=\"wp-block-heading\">Tanto <code>asyncio<\/code> quanto <code>threading<\/code> comportam-se de forma concorrente em Python<\/h3>\n<p>De maneira geral, as bibliotecas Python <code>asyncio<\/code> e <code>threading<\/code> permitem a concorr\u00eancia. No entanto, suas CPUs n\u00e3o est\u00e3o realizando v\u00e1rias tarefas simultaneamente. Apenas parece que sim.<\/p>\n<p>Imagine que voc\u00ea est\u00e1 oferecendo um jantar com v\u00e1rios pratos para alguns convidados. Alguns pratos demoram mais tempo para serem preparados, como a torta que precisa ser assada no forno ou a sopa que precisa ser cozida no fog\u00e3o. Enquanto aguardamos que cozinhem, n\u00e3o ficamos parados de bra\u00e7os cruzados. Faremos outras coisas nesse meio-tempo. Isso \u00e9 semelhante \u00e0 concorr\u00eancia em Python. \u00c0s vezes, seu processo Python fica aguardando que algo seja conclu\u00eddo. Por exemplo, alguns processos de entrada\/sa\u00edda (E\/S) est\u00e3o sendo processados pelo sistema operacional e, nesse momento, o processo Python est\u00e1 apenas aguardando. Podemos ent\u00e3o utilizar async para permitir que outro processo Python seja executado enquanto aguarda.<\/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 x asyncio no Python\" width=\"1600\" height=\"896\" \/><\/figure>\n<h3 id=\"the-difference-is-who-is-in-charge\" class=\"wp-block-heading\">A diferen\u00e7a est\u00e1 em quem est\u00e1 no comando<\/h3>\n<p>Se tanto <code>asyncio<\/code> quanto <code>threading<\/code> parecem ser concorrentes, qual \u00e9 a diferen\u00e7a entre eles? Bem, a principal diferen\u00e7a \u00e9 uma quest\u00e3o de quem \u00e9 respons\u00e1vel por qual processo est\u00e1 sendo executado e quando. Para <code>async\/await<\/code>, a abordagem \u00e9 \u00e0s vezes chamada de concorr\u00eancia cooperativa. Uma corrotina ou futuro cede seu controle a outra corrotina ou futuro para permitir que outros tenham uma oportunidade. Por outro lado, em <code>threading<\/code>, o gerenciador do sistema operacional controlar\u00e1 qual processo est\u00e1 em execu\u00e7\u00e3o.<\/p>\n<p>A concorr\u00eancia cooperativa \u00e9 como uma reuni\u00e3o em que um microfone \u00e9 passado de pessoa para pessoa para que cada uma possa falar. Quem estiver com o microfone pode falar e, quando terminar ou n\u00e3o tiver mais nada a dizer, deve passar o microfone para a pr\u00f3xima pessoa. Em contraste, multithreading \u00e9 uma reuni\u00e3o em que h\u00e1 um presidente que determina quem tem a palavra em um determinado momento.\u00a0<\/p>\n<h2 id=\"writing-concurrent-code-in-python\" class=\"wp-block-heading\">Escrevendo c\u00f3digo simult\u00e2neo em Python<\/h2>\n<p>Vamos examinar como a concorr\u00eancia funciona em Python, escrevendo alguns exemplos de c\u00f3digo. Criaremos uma simula\u00e7\u00e3o de restaurante fast food utilizando tanto <code>asyncio<\/code> quanto <code>threading<\/code>.<\/p>\n<h3 id=\"how-async-await-works-in-python\" class=\"wp-block-heading\">Como funciona <code>async\/await<\/code> em Python<\/h3>\n<p>O pacote <code>asyncio<\/code> foi introduzido no Python 3.4, enquanto as palavras-chave <code>async<\/code> e <code>await<\/code> foram introduzidas no Python 3.5. Um dos principais fatores que possibilitam o uso de <code>async\/await<\/code> \u00e9 o uso de corrotinas. Corrotinas em Python s\u00e3o, na verdade, geradores reaproveitados para poder pausar e retornar \u00e0 fun\u00e7\u00e3o principal.<\/p>\n<p>Agora, imagine uma hamburgueria onde apenas um funcion\u00e1rio est\u00e1 trabalhando. Os pedidos s\u00e3o preparados de acordo com uma fila do tipo primeiro a entrar, primeiro a sair, e nenhuma opera\u00e7\u00e3o ass\u00edncrona pode ser realizada:<\/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>Isso levar\u00e1 algum tempo para ser conclu\u00eddo:<\/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>Agora, imagine que o restaurante contrata mais funcion\u00e1rios para que o trabalho possa ser realizado simultaneamente:<\/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>Observamos a diferen\u00e7a entre os dois:<\/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>Utilizando as fun\u00e7\u00f5es fornecidas por <code>asyncio<\/code>, como <code>run<\/code> e <code>gather<\/code>, e as palavras-chave <code>async<\/code> e <code>await<\/code>, criamos corrotinas que podem preparar hamb\u00fargueres simultaneamente.<\/p>\n<p>Agora, vamos dar um passo adiante e criar uma simula\u00e7\u00e3o mais complexa. Imagine que temos apenas dois funcion\u00e1rios e que podemos preparar apenas dois hamb\u00fargueres por vez.<\/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>Aqui, utilizaremos uma fila para armazenar as tarefas, e os funcion\u00e1rios ir\u00e3o busc\u00e1-las.<\/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>Neste exemplo, utilizamos <code>asyncio.Queue<\/code> para armazenar as tarefas, mas ser\u00e1 mais \u00fatil se tivermos v\u00e1rios tipos de tarefas, conforme demonstrado no exemplo a seguir.<\/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>Neste exemplo, h\u00e1 v\u00e1rias tarefas, incluindo fazer batatas fritas, que leva menos tempo, e anotar pedidos, que envolve obter informa\u00e7\u00f5es do usu\u00e1rio.\u00a0<\/p>\n<p>Observe que o programa interrompe a espera pela entrada do usu\u00e1rio e at\u00e9 mesmo os outros funcion\u00e1rios que n\u00e3o est\u00e3o atendendo o pedido, param de trabalhar em segundo plano. Isso ocorre porque a fun\u00e7\u00e3o <code>input<\/code> n\u00e3o \u00e9 ass\u00edncrona (async) e, portanto, n\u00e3o \u00e9 aguardada (await). Lembre-se de que o controle no c\u00f3digo ass\u00edncrono s\u00f3 \u00e9 liberado quando \u00e9 aguardado. Para corrigir isso, podemos substituir:<\/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>Por\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>E fazemos o mesmo com as batatas fritas \u2013 consulte o c\u00f3digo abaixo. Observe que agora o programa est\u00e1 em um loop infinito. Se precisarmos interromper, podemos deliberadamente encerrar o programa com uma entrada inv\u00e1lida.<\/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>Ao utilizar <code>asyncio.to_thread<\/code>, colocamos a fun\u00e7\u00e3o <code>input<\/code> em um thread separado (<a href=\"https:\/\/docs.python.org\/3\/library\/asyncio-task.html#running-in-threads\" target=\"_blank\" rel=\"noopener\">consulte esta refer\u00eancia<\/a>). Observe, no entanto, que este truque apenas desbloqueia tarefas limitadas por E\/S se o Python GIL estiver presente.<\/p>\n<p>Ao executar o c\u00f3digo acima, voc\u00ea tamb\u00e9m poder\u00e1 observar que a E\/S padr\u00e3o no terminal est\u00e1 embaralhada. A E\/S do usu\u00e1rio e o registro do que est\u00e1 ocorrendo devem ser separados. Podemos inserir o registro em um log para an\u00e1lise posterior.\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>Neste bloco de c\u00f3digo final, registramos as informa\u00e7\u00f5es da simula\u00e7\u00e3o em <code>pyburger.log<\/code> e reservamos o terminal para mensagens aos clientes. Tamb\u00e9m detectamos entradas inv\u00e1lidas durante o processo de pedido e alteramos um sinalizador <code>closing<\/code> para <code>True<\/code> se a entrada for inv\u00e1lida, presumindo que o usu\u00e1rio deseja sair. Quando o sinalizador <code>closing<\/code> for definido como <code>True<\/code>, o worker ir\u00e1 <code>retornar<\/code>, encerrando o loop infinito <code>while<\/code> da corrotina.<\/p>\n<h3 id=\"how-does-threading-work-in-python\" class=\"wp-block-heading\">Como funciona o <code>threading<\/code> em Python?<\/h3>\n<p>No exemplo acima, colocamos uma tarefa vinculada a E\/S em outro thread. Voc\u00ea pode se perguntar se podemos colocar todas as tarefas em threads separadas e deixar que elas executem simultaneamente. Vamos tentar utilizar <code>threading<\/code> em vez de <code>asyncio<\/code>.<\/p>\n<p>Considere o c\u00f3digo abaixo, onde criamos hamb\u00fargueres simultaneamente, sem nenhuma limita\u00e7\u00e3o:<\/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>No primeiro loop <code>for<\/code> em <code>main<\/code>, as tarefas s\u00e3o criadas em diferentes threads e s\u00e3o iniciadas. O segundo loop <code>for<\/code> garante que todos os hamb\u00fargueres sejam preparados antes que o programa prossiga (ou seja, antes de retornar a <code>main<\/code>).<\/p>\n<p>\u00c9 mais complexo quando contamos com apenas dois funcion\u00e1rios. Cada membro da equipe \u00e9 representado por um t\u00f3pico e eles receber\u00e3o tarefas de uma lista normal onde todas est\u00e3o armazenadas.<\/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>Ao executar o c\u00f3digo acima, pode ocorrer um erro em um dos threads, informando que est\u00e1 tentando obter uma tarefa de uma lista vazia. Voc\u00ea pode se perguntar por que isso ocorre, uma vez que temos uma condi\u00e7\u00e3o no loop <code>while<\/code> que faz com que ele continue apenas se o <code>task_queue<\/code> n\u00e3o estiver vazio. No entanto, ainda recebemos um erro porque encontramos condi\u00e7\u00f5es de corrida.<\/p>\n<h3 id=\"race-conditions\" class=\"wp-block-heading\">Condi\u00e7\u00f5es de corrida<\/h3>\n<p>Condi\u00e7\u00f5es de corrida podem ocorrer quando v\u00e1rias threads tentam acessar o mesmo recurso ou dados ao mesmo tempo e causam problemas no sistema. O momento e a ordem em que o recurso \u00e9 acessado s\u00e3o importantes para a l\u00f3gica do programa, e tempos imprevis\u00edveis ou o entrela\u00e7amento de v\u00e1rias threads acessando e modificando dados compartilhados podem causar erros.<\/p>\n<p>Para resolver a condi\u00e7\u00e3o de corrida em nosso programa, implementaremos um bloqueio no <code>task_queue<\/code>:<\/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>Para trabalhar, precisamos garantir que temos direitos de acesso \u00e0 fila ao verificar seu comprimento e obter tarefas dela. Enquanto possu\u00edmos os direitos, outros threads n\u00e3o poder\u00e3o acessar a fila:<\/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>Ao comparar os dois trechos de c\u00f3digo utilizando <code>asyncio<\/code> e <code>threading<\/code>, os resultados devem ser semelhantes. Voc\u00ea pode estar se perguntando qual \u00e9 o melhor e por que deve escolher um em detrimento do outro.<\/p>\n<p>Na pr\u00e1tica, escrever c\u00f3digo <code>asyncio<\/code> \u00e9 mais f\u00e1cil do que multithreading, pois n\u00e3o precisamos nos preocupar com poss\u00edveis condi\u00e7\u00f5es de corrida e deadlocks. Os controles s\u00e3o transmitidos entre corrotinas por padr\u00e3o, portanto, n\u00e3o s\u00e3o necess\u00e1rios bloqueios. No entanto, os threads Python t\u00eam o potencial de ser executados em paralelo, mas n\u00e3o na maioria das vezes com o GIL em vigor. Podemos revisitar este assunto quando discutirmos Python nogil (sem threads) na pr\u00f3xima postagem do blog.<\/p>\n<h2 id=\"benefiting-from-concurrency\" class=\"wp-block-heading\">Beneficiando-se da concorr\u00eancia<\/h2>\n<p>Por que desejamos utilizar concorr\u00eancia na programa\u00e7\u00e3o? H\u00e1 uma raz\u00e3o principal: a velocidade. Como ilustramos acima, as tarefas podem ser conclu\u00eddas mais rapidamente se conseguirmos reduzir o tempo de espera. Existem diferentes tipos de espera na computa\u00e7\u00e3o e, para cada um deles, tendemos a utilizar diferentes m\u00e9todos para economizar tempo.<\/p>\n<h3 id=\"i-o-bound-tasks\" class=\"wp-block-heading\">Tarefas vinculadas a E\/S<\/h3>\n<p>Uma tarefa ou programa \u00e9 considerada limitada por entrada\/sa\u00edda (E\/S) quando sua velocidade de execu\u00e7\u00e3o \u00e9 limitada principalmente pela velocidade das opera\u00e7\u00f5es de E\/S, como leitura de um arquivo ou rede, ou espera pela entrada do usu\u00e1rio. As opera\u00e7\u00f5es de E\/S s\u00e3o geralmente mais lentas do que outras opera\u00e7\u00f5es da CPU e, portanto, tarefas que envolvem muitas delas podem levar significativamente mais tempo. Exemplos t\u00edpicos dessas tarefas incluem a leitura de dados de um banco de dados, o tratamento de solicita\u00e7\u00f5es da Web ou o trabalho com arquivos grandes.<\/p>\n<p>O uso da concorr\u00eancia <code>async\/await<\/code> pode ajudar a otimizar o tempo de espera durante tarefas vinculadas a E\/S, desbloqueando a sequ\u00eancia de processamento e permitindo que outras tarefas sejam realizadas enquanto se aguarda.<\/p>\n<p>A concorr\u00eancia com <code>async\/await<\/code> \u00e9 ben\u00e9fica em muitos aplicativos Python, como aplicativos Web que envolvem muita comunica\u00e7\u00e3o com bancos de dados e tratamento de solicita\u00e7\u00f5es Web. As GUIs (interfaces gr\u00e1ficas do usu\u00e1rio) tamb\u00e9m podem se beneficiar da concorr\u00eancia <code>async\/await<\/code>, permitindo que tarefas em segundo plano sejam executadas enquanto o usu\u00e1rio interage com o aplicativo.<\/p>\n<h3 id=\"cpu-bound-tasks\" class=\"wp-block-heading\">Tarefas vinculadas \u00e0 CPU<\/h3>\n<p>Uma tarefa ou programa \u00e9 considerada dependente da CPU quando sua velocidade de execu\u00e7\u00e3o \u00e9 limitada principalmente pela velocidade da CPU. Exemplos t\u00edpicos incluem processamento de imagens ou v\u00eddeos, como redimensionamento ou edi\u00e7\u00e3o, e c\u00e1lculos matem\u00e1ticos complexos, como multiplica\u00e7\u00e3o de matrizes ou treinamento de modelos de machine learning.<\/p>\n<p>Ao contr\u00e1rio das tarefas vinculadas a E\/S, as tarefas vinculadas \u00e0 CPU raramente podem ser otimizadas usando concorr\u00eancia com <code>async\/await<\/code>, pois a CPU j\u00e1 est\u00e1 ocupada trabalhando nas tarefas. Se voc\u00ea possui mais de uma CPU em sua m\u00e1quina ou se pode transferir algumas dessas tarefas para uma ou mais GPUs, as tarefas vinculadas \u00e0 CPU podem ser conclu\u00eddas mais rapidamente criando mais threads e executando multiprocessamento. O multiprocessamento pode otimizar a forma como essas CPUs e GPUs s\u00e3o utilizadas, raz\u00e3o pela qual muitos modelos de machine learning e IA atualmente s\u00e3o treinados em v\u00e1rias GPUs.<\/p>\n<p>No entanto, isso \u00e9 dif\u00edcil de realizar com c\u00f3digo Python puro, pois o Python em si foi projetado para fornecer camadas abstratas, para que os usu\u00e1rios n\u00e3o precisem controlar os processos de computa\u00e7\u00e3o de n\u00edvel inferior. Al\u00e9m disso, o GIL do Python limita o compartilhamento de recursos Python entre v\u00e1rias threads no seu computador. Recentemente, o Python 3.13 possibilitou a remo\u00e7\u00e3o do GIL, permitindo o multithreading verdadeiro. Discutiremos o GIL e a possibilidade de prescindir dele na pr\u00f3xima publica\u00e7\u00e3o do blog.<\/p>\n<p>\u00c0s vezes, nenhum dos m\u00e9todos mencionados acima \u00e9 capaz de acelerar suficientemente as tarefas vinculadas \u00e0 CPU. Nesse caso, as tarefas vinculadas \u00e0 CPU podem precisar ser divididas em tarefas menores para que possam ser executadas simultaneamente em v\u00e1rios threads, v\u00e1rios processadores ou at\u00e9 mesmo v\u00e1rias m\u00e1quinas. Isso \u00e9 processamento paralelo, e talvez seja necess\u00e1rio reescrever completamente o c\u00f3digo para implement\u00e1-lo. Em Python, o pacote <code>multiprocessing<\/code> oferece concorr\u00eancia local e remota, que pode ser usada para contornar a limita\u00e7\u00e3o do GIL. Tamb\u00e9m analisaremos alguns exemplos disso na pr\u00f3xima publica\u00e7\u00e3o do blog.<\/p>\n<h2 id=\"debugging-concurrent-code-in-py-charm\" class=\"wp-block-heading\">Depura\u00e7\u00e3o de c\u00f3digo concorrente no PyCharm<\/h2>\n<p>A depura\u00e7\u00e3o de c\u00f3digo ass\u00edncrono ou concorrente pode ser complexa, pois o programa n\u00e3o \u00e9 executado em sequ\u00eancia, o que dificulta a identifica\u00e7\u00e3o do local e do momento em que o c\u00f3digo est\u00e1 sendo executado. Muitos desenvolvedores utilizam <code>print<\/code> para auxiliar no rastreamento do fluxo do c\u00f3digo, mas essa abordagem n\u00e3o \u00e9 recomendada, pois \u00e9 muito desajeitada e n\u00e3o \u00e9 f\u00e1cil utiliz\u00e1-la para investigar um programa complexo, como um programa concorrente. Al\u00e9m disso, \u00e9 complicado arrumar tudo depois.<\/p>\n<p>Muitos IDEs oferecem depuradores, que s\u00e3o excelentes para inspecionar vari\u00e1veis e o fluxo do programa. Os depuradores tamb\u00e9m fornecem um rastreamento de pilha claro em m\u00faltiplos threads. Vamos verificar como podemos rastrear a <code>task_queue<\/code> da nossa simula\u00e7\u00e3o de restaurante de exemplo no <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>Primeiramente, definiremos alguns pontos de interrup\u00e7\u00e3o em nosso c\u00f3digo. Voc\u00ea pode fazer isso clicando no n\u00famero da linha onde deseja que o depurador pause. O n\u00famero da linha se transformar\u00e1 em um ponto vermelho, indicando que um ponto de interrup\u00e7\u00e3o foi definido nesse local. Iremos inserir pontos de interrup\u00e7\u00e3o nas linhas 23, 27 e 65, onde <code>task_queue<\/code> \u00e9 alterado em diferentes 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>Em seguida, podemos executar o programa no modo de depura\u00e7\u00e3o clicando no pequeno \u00edcone de besouro no canto superior direito.<\/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>Ap\u00f3s clicar no \u00edcone, a janela <em>Debug<\/em> ser\u00e1 aberta. O programa ser\u00e1 executado at\u00e9 atingir o primeiro ponto de interrup\u00e7\u00e3o destacado no c\u00f3digo.<\/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>Aqui, observamos que o thread <code>John<\/code> est\u00e1 tentando selecionar a tarefa, e a linha 65 est\u00e1 real\u00e7ada. Neste momento, a linha real\u00e7ada ainda n\u00e3o foi executada. Isso \u00e9 \u00fatil quando desejamos inspecionar as vari\u00e1veis antes de inserir o ponto de interrup\u00e7\u00e3o.<\/p>\n<p>Vamos verificar o que est\u00e1 em <code>task_queue<\/code>. Para isso, basta come\u00e7ar a digitar na janela <em>Debug<\/em>, conforme mostrado abaixo.<\/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>Selecione ou digite \u201ctask_queue\u201d e pressione <em>Enter<\/em>. Voc\u00ea observar\u00e1 que a tarefa <code>take_order<\/code> est\u00e1 na fila.<\/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>Agora, vamos executar o ponto de interrup\u00e7\u00e3o clicando no bot\u00e3o <em>Step in<\/em>, conforme mostrado abaixo.<\/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>Ap\u00f3s pressionar e inspecionar a janela <em>Special Variables<\/em> que aparece, observamos que a vari\u00e1vel da tarefa agora \u00e9 <code>take_order<\/code> na 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>Ao consultar novamente <code>task_queue<\/code>, observamos que agora a lista est\u00e1 vazia.<\/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>Agora, clique no bot\u00e3o <em>Resume Program<\/em> e deixe o programa ser executado.<\/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>Quando o programa chegar \u00e0 parte de entrada do usu\u00e1rio, o PyCharm nos levar\u00e1 \u00e0 janela <em>Console<\/em> para que possamos fornecer a entrada. Digamos que desejamos dois hamb\u00fargueres. Digite \u201c2\u201d e pressione <em>Enter<\/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>Agora chegamos ao segundo ponto de interrup\u00e7\u00e3o. Se clicarmos em <em>Threads &amp; Variables<\/em> para retornar \u00e0 janela anterior, observaremos que <code>burger_num<\/code> \u00e9 dois, conforme inserido.<\/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>Agora, vamos entrar no ponto de interrup\u00e7\u00e3o e inspecionar o <code>task_queue<\/code>, como fizemos anteriormente. Observamos que uma tarefa <code>make_burger<\/code> foi adicionada.<\/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>Executamos o programa novamente e, se entrarmos no ponto de interrup\u00e7\u00e3o quando ele parar, observamos que <code>Jane<\/code> est\u00e1 assumindo a tarefa.<\/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>\u00c9 poss\u00edvel examinar o restante do c\u00f3digo por conta pr\u00f3pria. Quando terminar, basta pressionar o bot\u00e3o vermelho <em>Stop <\/em> na parte superior da janela.<\/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>Com o depurador no PyCharm, \u00e9 poss\u00edvel acompanhar a execu\u00e7\u00e3o do seu programa em diferentes threads e inspecionar diferentes vari\u00e1veis com facilidade.<\/p>\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n<h2 id=\"conclusion\" class=\"wp-block-heading\">Conclus\u00e3o<\/h2>\n<p>Agora que aprendemos os conceitos b\u00e1sicos de concorr\u00eancia em Python, espero que voc\u00ea consiga domin\u00e1-los com a pr\u00e1tica. Na pr\u00f3xima postagem do blog, examinaremos o GIL do Python, sua fun\u00e7\u00e3o e o que muda quando ele est\u00e1 ausente.<\/p>\n<p>O PyCharm oferece ferramentas poderosas para trabalhar com c\u00f3digo Python simult\u00e2neo. Conforme demonstrado nesta publica\u00e7\u00e3o do blog, o depurador permite a inspe\u00e7\u00e3o passo a passo de c\u00f3digo ass\u00edncrono e com threads, auxiliando no rastreamento do fluxo de execu\u00e7\u00e3o, no monitoramento de recursos compartilhados e na detec\u00e7\u00e3o de problemas. Com pontos de interrup\u00e7\u00e3o intuitivos, visualiza\u00e7\u00f5es de vari\u00e1veis em tempo real, integra\u00e7\u00e3o perfeita com o console para entrada do usu\u00e1rio e suporte robusto a registros, o PyCharm facilita a escrita, o teste e a depura\u00e7\u00e3o de aplicativos com confian\u00e7a e clareza.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\"><a class=\"btn\" href=\"https:\/\/www.jetbrains.com\/pycharm\/\" target=\"\" rel=\"noopener\">Baixe o PyCharm agora<\/a><\/div>\n<\/div>\n\n\n<p><em>Artigo original em ingl\u00eas por:<\/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":1086,"featured_media":591093,"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\/pt-br\/wp-json\/wp\/v2\/pycharm\/591088"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/pycharm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/types\/pycharm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/users\/1086"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/comments?post=591088"}],"version-history":[{"count":9,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/pycharm\/591088\/revisions"}],"predecessor-version":[{"id":591110,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/pycharm\/591088\/revisions\/591110"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/media\/591093"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/media?parent=591088"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/categories?post=591088"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/tags?post=591088"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/pt-br\/wp-json\/wp\/v2\/cross-post-tag?post=591088"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}