{"id":593110,"date":"2025-08-18T15:37:13","date_gmt":"2025-08-18T14:37:13","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=593110"},"modified":"2025-10-16T11:01:24","modified_gmt":"2025-10-16T10:01:24","slug":"python-mas-rapido-desbloqueamos-el-global-interpreter-lock-de-python","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/es\/pycharm\/2025\/08\/python-mas-rapido-desbloqueamos-el-global-interpreter-lock-de-python\/","title":{"rendered":"Python m\u00e1s r\u00e1pido: desbloqueamos el Global Interpreter Lock de Python"},"content":{"rendered":"<h2 class=\"wp-block-heading\" id=\"what-is-pythons-global-interpreter-lock-gil\">\u00bfQu\u00e9 es el Global Interpreter Lock (GIL) de Python?<\/h2>\n<p>\u00abGlobal Interpreter Lock\u00bb (o \u00abGIL\u00bb) es un t\u00e9rmino familiar en la comunidad Python. Se trata de una funcionalidad muy conocida de Python. Pero \u00bfqu\u00e9 es exactamente un GIL?<\/p>\n<p>Si tiene experiencia con otros lenguajes de programaci\u00f3n (Rust, por ejemplo), puede que ya sepa lo que es un mutex. Es la abreviatura de \u00abexclusi\u00f3n mutua\u00bb. Un mutex asegura que solo puede acceder a los datos un hilo al mismo tiempo. Esto evita que los datos sean modificados por varios hilos a la vez. Puede pensar en ello como un tipo de \u00abbloqueo\u00bb: bloquea todos los hilos para que no puedan acceder a los datos, excepto el hilo que tiene la llave.<\/p>\n<p>El GIL es t\u00e9cnicamente un mutex. Permite que solo un hilo tenga acceso al int\u00e9rprete de Python a la vez. A veces me lo imagino como un volante para Python. \u00a1Nadie quiere tener a m\u00e1s de una persona al mando del volante! Sin embargo, un grupo de personas en un viaje por carretera cambiar\u00e1 a menudo de conductor. Esto es algo as\u00ed como transferir el acceso al int\u00e9rprete a un hilo diferente.<\/p>\n<p>Debido a su GIL, Python no permite verdaderos procesos multihilo. Esta funcionalidad ha suscitado debates en la \u00faltima d\u00e9cada, y ha habido muchos intentos de hacer Python m\u00e1s r\u00e1pido eliminando el GIL y permitiendo procesos multihilo. Recientemente, en Python 3.13, se ha puesto en marcha una opci\u00f3n que permite utilizar Python sin el GIL, a veces conocido como Python sin GIL, o Python de hilo libre. As\u00ed pues, comienza una nueva era de la programaci\u00f3n en Python.<\/p>\n<h2 class=\"wp-block-heading\" id=\"why-was-the-gil-there-in-the-first-place\">\u00bfPor qu\u00e9 se instaur\u00f3 el GIL inicialmente?<\/h2>\n<p>Si el GIL es tan impopular, \u00bfpor qu\u00e9 se implement\u00f3 en primer lugar? En realidad, tener un GIL tiene sus ventajas. En otros lenguajes de programaci\u00f3n con multihilo real, a veces se producen problemas porque m\u00e1s de un hilo modifica los datos, y el resultado final depende de qu\u00e9 hilo o proceso termine primero. Esto se denomina \u00abcondici\u00f3n de carrera\u00bb. Los lenguajes como Rust suelen ser dif\u00edciles de aprender porque los programadores tienen que utilizar mutex para evitar condiciones de carrera.<\/p>\n<p>En Python, todos los objetos tienen un contador de referencias para llevar la cuenta de cu\u00e1ntos otros objetos requieren informaci\u00f3n de ellos. Si el contador de referencias llega a cero, como sabemos que no existe ninguna condici\u00f3n de carrera en Python debido al GIL, podemos declarar con confianza que el objeto ya no es necesario y puede ser recogido por el recolector de basura.<\/p>\n<p>Cuando Python se lanz\u00f3 en 1991, la mayor\u00eda de los ordenadores personales solo ten\u00edan un n\u00facleo, y no muchos programadores solicitaban compatibilidad con multihilo. Disponer de un GIL resuelve muchos problemas en la implementaci\u00f3n de programas, y tambi\u00e9n puede facilitar el mantenimiento del c\u00f3digo. Por ello, Guido van Rossum (el creador de Python) a\u00f1adi\u00f3 un GIL en 1992.<\/p>\n<p>Avancemos hasta 2025: los ordenadores personales tienen procesadores multin\u00facleo y, por tanto, mucha m\u00e1s potencia de computaci\u00f3n. Podemos aprovechar esta potencia adicional para lograr una verdadera concurrencia sin deshacernos del GIL.<\/p>\n<p>M\u00e1s adelante en este art\u00edculo, desglosaremos el proceso de eliminaci\u00f3n. Pero por ahora, veamos c\u00f3mo funciona la verdadera concurrencia con el GIL en funcionamiento.<\/p>\n<h2 class=\"wp-block-heading\" id=\"multiprocessing-in-python\">Multiprocesamiento en Python<\/h2>\n<p>Antes de adentrarnos en el proceso de eliminaci\u00f3n del GIL, echemos un vistazo a c\u00f3mo los desarrolladores de Python pueden lograr una verdadera concurrencia utilizando la biblioteca de multiprocesamiento. La biblioteca de multiprocesamiento est\u00e1ndar ofrece concurrencia tanto local como remota, eludiendo eficazmente el bloqueo global del int\u00e9rprete mediante el uso de subprocesos en lugar de hilos. De este modo, el m\u00f3dulo de multiprocesamiento permite al programador aprovechar al m\u00e1ximo los m\u00faltiples procesadores de una m\u00e1quina determinada.<\/p>\n<p>Sin embargo, para realizar el multiprocesamiento, tendremos que dise\u00f1ar nuestro programa de forma un poco diferente. Veamos el siguiente ejemplo de c\u00f3mo utilizar la biblioteca de<em> <\/em>multiprocesamiento en Python.<br \/>Recuerde nuestra hamburgueser\u00eda as\u00edncrona de la <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/06\/concurrency-in-async-await-and-threading\/\">primera parte de la serie del blog<\/a>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import asyncio\n\nimport time\n\nasync def make_burger(order_num):\n\n    print(f\"Preparing burger #{order_num}...\")\n\n    await asyncio.sleep(5) # time for making the burger\n\n    print(f\"Burger made #{order_num}\")\n\nasync def main():\n\n    order_queue = []\n\n    for i in range(3):\n\n        order_queue.append(make_burger(i))\n\n    await asyncio.gather(*(order_queue))\n\nif __name__ == \"__main__\":\n\n    s = time.perf_counter()\n\n    asyncio.run(main())\n\n    elapsed = time.perf_counter() - s\n\n    print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>Podemos utilizar la biblioteca de multiprocesamiento para hacer lo mismo, por ejemplo:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import multiprocessing\n\n\nimport time\n\n\ndef make_burger(order_num):\n\n\n   print(f\"Preparing burger #{order_num}...\")\n\n\n   time.sleep(5) # time for making the burger\n\n\n   print(f\"Burger made #{order_num}\")\n\n\n\n\nif __name__ == \"__main__\":\n\n\n   print(\"Number of available CPU:\", multiprocessing.cpu_count())\n\n\n\n\n   s = time.perf_counter()\n\n\n   all_processes = []\n\n\n   for i in range(3):\n       process = multiprocessing.Process(target=make_burger, args=(i,))\n       process.start()\n       all_processes.append(process)\n\n\n   for process in all_processes:\n       process.join()\n\n\n   elapsed = time.perf_counter() - s\n\n\n   print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>Como recordar\u00e1, muchos de los m\u00e9todos del multiprocesamiento son muy similares a los hilos. Para ver la diferencia en el multiprocesamiento<em>,<\/em> exploremos un caso de uso m\u00e1s complejo:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import multiprocessing\nimport time\nimport queue\n\n\n\n\ndef make_burger(order_num, item_made):\n   name = multiprocessing.current_process().name\n   print(f\"{name} is preparing burger #{order_num}...\")\n   time.sleep(5)  # time for making burger\n   item_made.put(f\"Burger #{order_num}\")\n   print(f\"Burger #{order_num} made by {name}\")\n\n\n\n\ndef make_fries(order_num, item_made):\n   name = multiprocessing.current_process().name\n   print(f\"{name} is preparing fries #{order_num}...\")\n   time.sleep(2)  # time for making fries\n   item_made.put(f\"Fries #{order_num}\")\n   print(f\"Fries #{order_num} made by {name}\")\n\n\n\n\ndef working(task_queue, item_made, order_num, lock):\n   break_count = 0\n   name = multiprocessing.current_process().name\n   while True:\n       try:\n           task = task_queue.get_nowait()\n       except queue.Empty:\n           print(f\"{name} has nothing to do...\")\n           if break_count &gt; 1:\n               break  # stop if idle for too long\n           else:\n               break_count += 1\n           time.sleep(1)\n       else:\n           lock.acquire()\n           try:\n               current_num = order_num.value\n               order_num.value = current_num + 1\n           finally:\n               lock.release()\n           task(current_num, item_made)\n           break_count = 0\n\n\n\n\nif __name__ == \"__main__\":\n\n\n   print(\"Welcome to Pyburger! Please place your order.\")\n\n\n   burger_num = input(\"Number of burgers:\")\n   fries_num = input(\"Number of fries:\")\n\n\n   s = time.perf_counter()\n\n\n   task_queue = multiprocessing.Queue()\n   item_made = multiprocessing.Queue()\n   order_num = multiprocessing.Value(\"i\", 0)\n   lock = multiprocessing.Lock()\n\n\n   for i in range(int(burger_num)):\n       task_queue.put(make_burger)\n   for i in range(int(fries_num)):\n       task_queue.put(make_fries)\n\n\n   staff1 = multiprocessing.Process(\n       target=working,\n       name=\"John\",\n       args=(\n           task_queue,\n           item_made,\n           order_num,\n           lock,\n       ),\n   )\n   staff2 = multiprocessing.Process(\n       target=working,\n       name=\"Jane\",\n       args=(\n           task_queue,\n           item_made,\n           order_num,\n           lock,\n       ),\n   )\n\n\n   staff1.start()\n   staff2.start()\n\n\n   staff1.join()\n   staff2.join()\n\n\n   print(\"All tasks finished. Closing now.\")\n   print(\"Items created are:\")\n\n\n   while not item_made.empty():\n       print(item_made.get())\n\n\n   elapsed = time.perf_counter() - s\n\n\n   print(f\"Orders completed in {elapsed:0.2f} seconds.\")\n<\/pre>\n<p>Este es el resultado que obtenemos:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Welcome to Pyburger! Please place your order.\nNumber of burgers:3\nNumber of fries:2\nJane has nothing to do...\nJohn is preparing burger #0...\nJane is preparing burger #1...\nBurger #0 made by John\nJohn is preparing burger #2...\nBurger #1 made by Jane\nJane is preparing fries #3...\nFries #3 made by Jane\nJane is preparing fries #4...\nBurger #2 made by John\nJohn has nothing to do...\nFries #4 made by Jane\nJane has nothing to do...\nJohn has nothing to do...\nJane has nothing to do...\nJohn has nothing to do...\nJane has nothing to do...\nAll tasks finished. Closing now.\nItems created are:\nBurger #0\nBurger #1\nFries #3\nBurger #2\nFries #4\nOrders completed in 12.21 seconds.\n<\/pre>\n<p>Tenga en cuenta que existen algunas limitaciones en el multiprocesamiento<em> <\/em>que hacen que el c\u00f3digo anterior se haya dise\u00f1ado de esta forma. Vamos a repasarlas una por una.<\/p>\n<p>En primer lugar, recuerde que antes ten\u00edamos las funciones<em> <\/em>make_burger y make_fries para generar una funci\u00f3n con el orden_num correcto:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def make_burger(order_num):\n   def making_burger():\n       logger.info(f\"Preparing burger #{order_num}...\")\n       time.sleep(5)  # time for making burger\n       logger.info(f\"Burger made #{order_num}\")\n\n\n   return making_burger\n\n\n\n\ndef make_fries(order_num):\n   def making_fries():\n       logger.info(f\"Preparing fries #{order_num}...\")\n       time.sleep(2)  # time for making fries\n       logger.info(f\"Fries made #{order_num}\")\n\n\n   return making_fries\n<\/pre>\n<p>No podemos hacer lo mismo cuando utilizamos el multiprocesamiento. Si lo intentamos, obtendremos un error del tipo:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">AttributeError: Can't get local object 'make_burger..making_burger'<\/pre>\n<p>La raz\u00f3n es que el multiprocesamiento utiliza <a href=\"https:\/\/docs.python.org\/3\/library\/pickle.html\" target=\"_blank\" rel=\"noopener\">pickle<\/a>, que solo puede serializar funciones de nivel de m\u00f3dulo superior en general. Esta es una de las limitaciones del multiprocesamiento<em>.<\/em><\/p>\n<p>En segundo lugar, observe que en el fragmento de c\u00f3digo del ejemplo anterior que utiliza el multiprocesamiento no utilizamos ninguna variable global para los datos compartidos. Por ejemplo, no podemos utilizar variables globales para item_made y order_num. Para compartir datos entre distintos procesos, se utilizan objetos de clase especial como <em> <\/em>Queue y<em> <\/em>Value de la biblioteca de multiprocesamiento y se pasan a los procesos como argumentos.<br \/>En general, no se recomienda compartir datos y estados entre distintos procesos, ya que puede causar muchos m\u00e1s problemas. En nuestro ejemplo anterior, tenemos que utilizar un bloqueo para asegurarnos de que solo puede acceder e incrementar el valor de order_num un proceso a la vez. Sin el bloqueo, el n\u00famero de pedido del art\u00edculo puede desordenarse as\u00ed:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Items created are:\n\nBurger #0\nBurger #0\nFries #2\nBurger #1\nFries #3\n<\/pre>\n<p>Aqu\u00ed tenemos c\u00f3mo utilizar un bloqueo para evitar problemas:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">           lock.acquire()\n           try:\n               current_num = order_num.value\n               order_num.value = current_num + 1\n           finally:\n               lock.release()\n           task(current_num, item_made)\n<\/pre>\n<p>Para saber m\u00e1s sobre c\u00f3mo utilizar la biblioteca est\u00e1ndar de multiprocesamiento, puede consultar la <a href=\"https:\/\/docs.python.org\/3\/library\/multiprocessing.html\" target=\"_blank\" rel=\"noopener\">documentaci\u00f3n aqu\u00ed<\/a>.<\/p>\n<h2 class=\"wp-block-heading\" id=\"removing-the-gil\">Eliminaci\u00f3n del GIL<\/h2>\n<p>La eliminaci\u00f3n del GIL ha sido un tema de debate durante casi una d\u00e9cada. En 2016, en el Python Language Summit, Larry Hastings present\u00f3 sus reflexiones sobre la realizaci\u00f3n de una \u00abGIL-ectom\u00eda\u00bb en el int\u00e9rprete CPython y los progresos que hab\u00eda hecho con esta idea [1]. Este fue un intento pionero de eliminar el GIL de Python. En 2021, Sam Gross reaviv\u00f3 la discusi\u00f3n sobre la eliminaci\u00f3n del GIL [2], y eso condujo a la publicaci\u00f3n de <a href=\"https:\/\/peps.python.org\/pep-0703\/\" target=\"_blank\" rel=\"noopener\">PEP 703 &#8211; Making the Global Interpreter Lock Optional in CPython<\/a> en 2023.<\/p>\n<p>Como vemos, la supresi\u00f3n del GIL no es en absoluto una decisi\u00f3n precipitada y ha sido objeto de un amplio debate en la comunidad. Como demuestran los ejemplos anteriores de multiprocesamiento (y el PEP 703, enlazado m\u00e1s arriba), cuando se elimina la garant\u00eda proporcionada por el GIL, las cosas se complican r\u00e1pidamente.<\/p>\n<p>[1]: <a href=\"https:\/\/lwn.net\/Articles\/689548\/\" target=\"_blank\" rel=\"noopener\">https:\/\/lwn.net\/Articles\/689548\/<\/a><\/p>\n<p>[2]: https:\/\/lwn.net\/ml\/python-dev\/CAGr09bSrMNyVNLTvFq-h6t38kTxqTXfgxJYApmbEWnT71L74-g@mail.gmail.com\/<\/p>\n<h3 class=\"wp-block-heading\" id=\"reference-counting\">Recuento de referencias<\/h3>\n<p>Cuando el GIL est\u00e1 presente, el recuento de referencias y la recogida de basura son m\u00e1s sencillos. Cuando solo un hilo a la vez tiene acceso a objetos Python, podemos confiar en un  <a href=\"https:\/\/en.wikipedia.org\/wiki\/Reference_counting\" target=\"_blank\" rel=\"noopener\">recuento de referencias<\/a> no at\u00f3mico directo y eliminar el objeto cuando el recuento de referencias llegue a cero.<\/p>\n<p>La eliminaci\u00f3n del GIL complica las cosas. Ya no podemos utilizar el recuento de referencias no at\u00f3mico, ya que no garantiza la <a href=\"https:\/\/en.wikipedia.org\/wiki\/Thread_safety\" target=\"_blank\" rel=\"noopener\">seguridad de los hilos<\/a>. Las cosas pueden ir mal si varios hilos est\u00e1n realizando varios incrementos y decrementos de la referencia en el objeto Python al mismo tiempo. Lo ideal ser\u00eda utilizar el recuento at\u00f3mico de referencias para garantizar la seguridad de los hilos. Pero este m\u00e9todo adolece de una elevada sobrecarga y su eficacia se ve mermada cuando hay muchos hilos.<\/p>\n<p>La soluci\u00f3n es utilizar el recuento de referencias sesgado, que tambi\u00e9n garantiza la seguridad de los hilos. La idea es inclinar cada objeto hacia un hilo propietario, que es el hilo que accede a ese objeto la mayor parte del tiempo. Los hilos propietarios pueden realizar un recuento de referencias no at\u00f3mico en los objetos que poseen, mientras que los dem\u00e1s hilos deben realizar un recuento de referencias at\u00f3mico en esos objetos. Este m\u00e9todo es preferible al simple recuento at\u00f3mico de referencias, porque la mayor\u00eda de los objetos solo reciben acceso a un hilo la mayor parte del tiempo. Podemos reducir la sobrecarga de ejecuci\u00f3n permitiendo que el hilo propietario realice un recuento de referencias no at\u00f3mico.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1280\" height=\"720\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-22.png\" alt=\"\" class=\"wp-image-586289\" \/><\/figure>\n<p>Adem\u00e1s, algunos objetos Python de uso com\u00fan, como True, False<em>, <\/em>enteros peque\u00f1os y algunas cadenas internadas, se hacen inmortales. Aqu\u00ed, \u00abinmortal\u00bb solo significa que los objetos permanecer\u00e1n en el programa durante toda su vida \u00fatil, por lo que no requieren recuento de referencias.<\/p>\n<h3 class=\"wp-block-heading\" id=\"garbage-collection\">Recolecci\u00f3n de basura<\/h3>\n<p>Tambi\u00e9n tenemos que modificar la forma en que se realiza la <a href=\"https:\/\/en.wikipedia.org\/wiki\/Garbage_collection_(computer_science)\" target=\"_blank\" rel=\"noopener\">recolecci\u00f3n de basura<\/a>. En lugar de disminuir el recuento de referencias inmediatamente cuando se libera la referencia y eliminar el objeto enseguida cuando el recuento de referencias llega a cero, se utiliza una t\u00e9cnica denominada \u00abrecuento de referencias diferido\u00bb.\u00a0<\/p>\n<p>Cuando es necesario disminuir el recuento de referencias, el objeto se almacena en una tabla, que se comprobar\u00e1 dos veces para ver si esta disminuci\u00f3n en el recuento de referencias es exacta o no. Esto evita eliminar el objeto prematuramente cuando a\u00fan est\u00e1 siendo referenciado, lo que puede ocurrir sin el GIL, ya que el recuento de referencias no es tan sencillo como con el GIL. Esto complica el proceso de recolecci\u00f3n de basura, ya que esta puede necesitar atravesar la <a href=\"https:\/\/en.wikipedia.org\/wiki\/Stack_(abstract_data_type)\" target=\"_blank\" rel=\"noopener\">pila<\/a> de cada hilo para el recuento de referencias propio de cada hilo.<\/p>\n<p>Otra cosa que tener en cuenta: el recuento de referencias debe ser estable durante la recolecci\u00f3n de basura. Si un objeto est\u00e1 a punto de ser descartado pero de repente se hace referencia a \u00e9l, esto causar\u00e1 graves problemas. Por ello, durante el ciclo de recolecci\u00f3n de basura, tendr\u00e1 que \u00abparar el mundo\u00bb para ofrecer garant\u00edas de seguridad de los hilos.<\/p>\n<h3 class=\"wp-block-heading\" id=\"memory-allocation\">Asignaci\u00f3n de memoria<\/h3>\n<p>Cuando existe el GIL para garantizar la seguridad de los hilos, se utiliza el asignador de memoria interno de Python pymalloc. Pero sin el GIL, necesitaremos un nuevo asignador de memoria. Sam Gross propuso <a href=\"https:\/\/github.com\/microsoft\/mimalloc\" target=\"_blank\" rel=\"noopener\">mimalloc<\/a> en el PEP, que es un asignador de uso general creado por Daan Leijen y mantenido por Microsoft. Es una buena elecci\u00f3n, porque es seguro para los hilos y tiene un buen rendimiento en objetos peque\u00f1os.<\/p>\n<p>Mimalloc llena su mont\u00f3n con p\u00e1ginas y las p\u00e1ginas con bloques<em>.<\/em> Cada p\u00e1gina contiene bloques, y los bloques<em> <\/em>dentro de cada p\u00e1gina tienen todos el mismo tama\u00f1o. Al a\u00f1adir algunas restricciones al acceso a la lista y al diccionario, el recolector de basura no tiene que mantener una <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linked_list\" target=\"_blank\" rel=\"noopener\">lista vinculada<\/a> para encontrar todos los objetos, y tambi\u00e9n permite el acceso de lectura a la<em> <\/em>lista y al diccionario sin adquirir el bloqueo.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1280\" height=\"720\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-23.png\" alt=\"\" class=\"wp-image-586300\" \/><\/figure>\n<p>Hay m\u00e1s detalles sobre la eliminaci\u00f3n del GIL, pero es imposible abarcarlos todos aqu\u00ed. Puede consultar <a href=\"https:\/\/peps.python.org\/pep-0703\/\" target=\"_blank\" rel=\"noopener\">PEP 703 \u2013 Making the Global Interpreter Lock Optional in CPython<\/a> para ver un desglose completo.<\/p>\n<h2 class=\"wp-block-heading\" id=\"difference-in-performance-with-and-without-the-gil\">Diferencia de rendimiento con el GIL y sin \u00e9l<\/h2>\n<p>Como Python 3.13 proporciona una opci\u00f3n de hilo libre, podemos comparar el rendimiento de la versi\u00f3n est\u00e1ndar de Python 3.13 con la versi\u00f3n de hilo libre.<\/p>\n<h3 class=\"wp-block-heading\" id=\"install-thread-free-python\">Instalar Python con hilos libres<\/h3>\n<p>Utilizaremos <a href=\"https:\/\/github.com\/pyenv\/pyenv\" target=\"_blank\" rel=\"noopener\">pyenv<\/a> para instalar ambas versiones: la est\u00e1ndar (por ejemplo, 3.13.5) y la versi\u00f3n con hilos libres (por ejemplo, 3.13.5t).\u00a0<\/p>\n<p>Tambi\u00e9n puede utilizar los instaladores de <a href=\"http:\/\/python.org\" target=\"_blank\" rel=\"noopener\">Python.org<\/a>. Aseg\u00farese de seleccionar la opci\u00f3n <em>Customize<\/em> durante la instalaci\u00f3n y marque la casilla adicional para instalar Python con hilos libres (<a href=\"https:\/\/til.simonwillison.net\/python\/trying-free-threaded-python\" target=\"_blank\" rel=\"noopener\">consulte el ejemplo de este art\u00edculo del blog<\/a>).<\/p>\n<p>Despu\u00e9s de instalar ambas versiones, podemos a\u00f1adirlas como int\u00e9rpretes en un proyecto PyCharm.<\/p>\n<p>En primer lugar, haga clic en el nombre de su int\u00e9rprete de Python en la parte inferior derecha.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"960\" height=\"328\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-24.png\" alt=\"\" class=\"wp-image-586311\" \/><\/figure>\n<p>Seleccione <em>Add New Interpreter<\/em> en el men\u00fa y, despu\u00e9s, <em>Add Local Interpreter<\/em>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"575\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-25.png\" alt=\"\" class=\"wp-image-586322\" \/><\/figure>\n<p>Elija <em>Select existing<\/em>, espere a que se cargue la ruta del int\u00e9rprete (lo que puede tardar un rato si tiene muchos int\u00e9rpretes como yo) y, a continuaci\u00f3n, seleccione el nuevo int\u00e9rprete que acaba de instalar en el men\u00fa desplegable <em>Python path<\/em>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1538\" height=\"1100\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-26.png\" alt=\"\" class=\"wp-image-586333\" \/><\/figure>\n<p>Pulse <em>OK<\/em> para a\u00f1adirlo. Repita los mismos pasos para el otro int\u00e9rprete. Ahora, cuando vuelva a hacer clic en el nombre del int\u00e9rprete en la parte inferior derecha, ver\u00e1 varios int\u00e9rpretes de Python 3.13, como en la imagen de arriba.<\/p>\n<h3 class=\"wp-block-heading\" id=\"testing-with-a-cpu-bounded-process\">Pruebas con un proceso limitado por la CPU<\/h3>\n<p>A continuaci\u00f3n, necesitamos un script para probar las diferentes versiones. Recuerde que en la <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/06\/concurrency-in-async-await-and-threading\/#cpu-bound-tasks\">parte 1 de esta serie de art\u00edculos del blog<\/a> explicamos que para acelerar los procesos limitados por la CPU necesitamos un verdadero multihilo. Para ver si la eliminaci\u00f3n del GIL permitir\u00e1 un verdadero multihilo y har\u00e1 que Python sea m\u00e1s r\u00e1pido, podemos hacer una prueba con un proceso limitado por la CPU en varios hilos. Aqu\u00ed est\u00e1 el guion que le ped\u00ed a <a href=\"https:\/\/www.jetbrains.com\/junie\/\" target=\"_blank\" rel=\"noopener\">Junie<\/a> que generara (con algunos ajustes finales por mi parte):<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import time\nimport multiprocessing  # Kept for CPU count\nfrom concurrent.futures import ThreadPoolExecutor\nimport sys\n\n\n\n\ndef is_prime(n):\n   \"\"\"Check if a number is prime (CPU-intensive operation).\"\"\"\n   if n &lt;= 1:\n       return False\n   if n &lt;= 3:\n       return True\n   if n % 2 == 0 or n % 3 == 0:\n       return False\n   i = 5\n   while i * i &lt;= n:\n       if n % i == 0 or n % (i + 2) == 0:\n           return False\n       i += 6\n   return True\n\n\n\n\ndef count_primes(start, end):\n   &quot;&quot;&quot;Count prime numbers in a range.&quot;&quot;&quot;\n   count = 0\n   for num in range(start, end):\n       if is_prime(num):\n           count += 1\n   return count\n\n\n\n\ndef run_single_thread(range_size, num_chunks):\n   &quot;&quot;&quot;Run the prime counting task in a single thread.&quot;&quot;&quot;\n   chunk_size = range_size \/\/ num_chunks\n   total_count = 0\n\n\n   start_time = time.time()\n\n\n   for i in range(num_chunks):\n       start = i * chunk_size + 1\n       end = (i + 1) * chunk_size + 1 if i &lt; num_chunks - 1 else range_size + 1\n       total_count += count_primes(start, end)\n\n\n   end_time = time.time()\n\n\n   return total_count, end_time - start_time\n\n\n\n\ndef thread_task(start, end):\n   &quot;&quot;&quot;Task function for threads.&quot;&quot;&quot;\n   return count_primes(start, end)\n\n\n\n\ndef run_multi_thread(range_size, num_threads, num_chunks):\n   &quot;&quot;&quot;Run the prime counting task using multiple threads.&quot;&quot;&quot;\n   chunk_size = range_size \/\/ num_chunks\n   total_count = 0\n\n\n   start_time = time.time()\n\n\n   with ThreadPoolExecutor(max_workers=num_threads) as executor:\n       futures = []\n       for i in range(num_chunks):\n           start = i * chunk_size + 1\n           end = (i + 1) * chunk_size + 1 if i &lt; num_chunks - 1 else range_size + 1\n           futures.append(executor.submit(thread_task, start, end))\n\n\n       for future in futures:\n           total_count += future.result()\n\n\n   end_time = time.time()\n\n\n   return total_count, end_time - start_time\n\n\n\n\ndef main():\n   # Fixed parameters\n   range_size = 1000000  # Range of numbers to check for primes\n   num_chunks = 16       # Number of chunks to divide the work into\n   num_threads = 4       # Fixed number of threads for multi-threading test\n\n\n   print(f&quot;Python version: {sys.version}&quot;)\n   print(f&quot;CPU count: {multiprocessing.cpu_count()}&quot;)\n   print(f&quot;Range size: {range_size}&quot;)\n   print(f&quot;Number of chunks: {num_chunks}&quot;)\n   print(&quot;-&quot; * 60)\n\n\n   # Run single-threaded version as baseline\n   print(&quot;Running single-threaded version (baseline)...&quot;)\n   count, single_time = run_single_thread(range_size, num_chunks)\n   print(f&quot;Found {count} primes in {single_time:.4f} seconds&quot;)\n   print(&quot;-&quot; * 60)\n\n\n   # Run multi-threaded version with fixed number of threads\n   print(f&quot;Running multi-threaded version with {num_threads} threads...&quot;)\n   count, thread_time = run_multi_thread(range_size, num_threads, num_chunks)\n   speedup = single_time \/ thread_time\n   print(f&quot;Found {count} primes in {thread_time:.4f} seconds (speedup: {speedup:.2f}x)&quot;)\n   print(&quot;-&quot; * 60)\n\n\n   # Summary\n   print(&quot;SUMMARY:&quot;)\n   print(f&quot;{&#039;Threads&#039;:&lt;10} {&#039;Time (s)&#039;:&lt;12} {&#039;Speedup&#039;:&lt;10}&quot;)\n   print(f&quot;{&#039;1 (baseline)&#039;:&lt;10} {single_time:&lt;12.4f} {&#039;1.00x&#039;:&lt;10}&quot;)\n   print(f&quot;{num_threads:&lt;10} {thread_time:&lt;12.4f} {speedup:.2f}x&quot;)\n\n\nif __name__ == &quot;__main__&quot;:\n   main()\n<\/pre>\n<p>Para facilitar la ejecuci\u00f3n del script con diferentes int\u00e9rpretes de Python, podemos a\u00f1adir un script de ejecuci\u00f3n personalizado a nuestro proyecto PyCharm.<\/p>\n<p>En la parte superior, seleccione <em>Edit Configurations\u2026<\/em> en el men\u00fa desplegable situado junto al bot\u00f3n <em>Run<\/em> (<img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/10\/AD_4nXcRz0HogcsseQcC85-vSwRJhBWhiZCysBUZjcuu1uQYusTx-hj6h-l4FAs_HUE1gcJV5kjSjPuUu1opfKgu6sH5G9epkDlMMw0fBRPyReunr-DgSpg5O_6QTIYROqOaiMHGAX42vg.png\" width=\"30\" height=\"23\">).<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/10\/AD_4nXeLJtj8xjnN-WQM7UQAop3RCg1hSElHa9WqFMBgXWGyxaxg07kOEhYnBV2i1WOnw5fUB_LsN-QWXidRJ-WMUVAkjF9aSVws684Jbnnoc8P-t_EDD622RghKpL5LjckKoKf251If3A.png\" width=\"391\" height=\"238\"><\/p>\n<p>Haga clic en el bot\u00f3n + de la parte superior izquierda y elija Python en el men\u00fa desplegable <em>Add New Configuration<\/em>.<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"680\" height=\"1130\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-27.png\" alt=\"\" class=\"wp-image-586344\" \/><\/figure>\n<p>Elija un nombre que le permita saber qu\u00e9 int\u00e9rprete concreto se est\u00e1 utilizando, por ejemplo, 3.13.5 frente a 3.15.3t. Elija el int\u00e9rprete adecuado y a\u00f1ada el nombre del script de prueba de esta forma:<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"1600\" height=\"1148\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/07\/image-28.png\" alt=\"\" class=\"wp-image-586355\" \/><\/figure>\n<p>A\u00f1ada dos configuraciones, una para cada int\u00e9rprete. A continuaci\u00f3n, pulse <em>OK<\/em>.<\/p>\n<p>Ahora podemos seleccionar y ejecutar f\u00e1cilmente el script de prueba con el GIL o sin \u00e9l, seleccionando la configuraci\u00f3n y haciendo clic en el bot\u00f3n <em>Run<\/em> (<img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/10\/AD_4nXcRz0HogcsseQcC85-vSwRJhBWhiZCysBUZjcuu1uQYusTx-hj6h-l4FAs_HUE1gcJV5kjSjPuUu1opfKgu6sH5G9epkDlMMw0fBRPyReunr-DgSpg5O_6QTIYROqOaiMHGAX42vg.png\" width=\"30\" height=\"23\">) de la parte superior.<\/p>\n<h3 class=\"wp-block-heading\" id=\"comparing-the-results\">Comparaci\u00f3n de los resultados<\/h3>\n<p>Este es el resultado que obtuve al ejecutar la versi\u00f3n est\u00e1ndar 3.13.5 con el GIL:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Python version: 3.13.5 (main, Jul 10 2025, 20:33:15) [Clang 17.0.0 (clang-1700.0.13.5)]\nCPU count: 8\nRange size: 1000000\nNumber of chunks: 16\n------------------------------------------------------------\nRunning single-threaded version (baseline)...\nFound 78498 primes in 1.1930 seconds\n------------------------------------------------------------\nRunning multi-threaded version with 4 threads...\nFound 78498 primes in 1.2183 seconds (speedup: 0.98x)\n------------------------------------------------------------\nSUMMARY:\nThreads    Time (s)     Speedup   \n1 (baseline) 1.1930       1.00x     \n4          1.2183       0.98x\n<\/pre>\n<p>Como puede ver, no hay ning\u00fan cambio significativo en la velocidad cuando se ejecuta la versi\u00f3n con 4 hilos en comparaci\u00f3n con la referencia de un solo hilo. Veamos qu\u00e9 obtenemos al ejecutar la versi\u00f3n con hilos libres 3.13.5t:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Python version: 3.13.5 experimental free-threading build (main, Jul 10 2025, 20:36:28) [Clang 17.0.0 (clang-1700.0.13.5)]\nCPU count: 8\nRange size: 1000000\nNumber of chunks: 16\n------------------------------------------------------------\nRunning single-threaded version (baseline)...\nFound 78498 primes in 1.5869 seconds\n------------------------------------------------------------\nRunning multi-threaded version with 4 threads...\nFound 78498 primes in 0.4662 seconds (speedup: 3.40x)\n------------------------------------------------------------\nSUMMARY:\nThreads    Time (s)     Speedup   \n1 (baseline) 1.5869       1.00x     \n4          0.4662       3.40x\n<\/pre>\n<p>Esta vez, la velocidad fue m\u00e1s de 3 veces superior. Observe que en ambos casos existe una sobrecarga por multihilo. Por lo tanto, incluso con un verdadero multihilo, la velocidad no es 4 veces mayor con 4 hilos.<\/p>\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusi\u00f3n<\/h2>\n<p>En la parte 2 de la serie de art\u00edculos del blog Faster Python, hablamos de la raz\u00f3n de tener el GIL de Python en el pasado, de sortear la limitaci\u00f3n del GIL utilizando el multiprocesamiento, y del proceso y efecto de eliminar el GIL.<\/p>\n<p>En el momento de publicar este art\u00edculo, la versi\u00f3n de Python con hilos libres a\u00fan no es la predeterminada. Sin embargo, con la adopci\u00f3n de la comunidad y de las bibliotecas de terceros, la comunidad espera que la versi\u00f3n de Python con hilos libres sea el est\u00e1ndar en el futuro. Se ha anunciado que Python 3.14 incluir\u00e1 una versi\u00f3n con hilos libres que habr\u00e1 superado la fase experimental, pero que seguir\u00e1 siendo opcional.<\/p>\n<p><a href=\"https:\/\/www.jetbrains.com\/pycharm\/\" target=\"_blank\" rel=\"noopener\">PyCharm<\/a> ofrece la mejor compatibilidad con Python de su clase para garantizar tanto la velocidad como la precisi\u00f3n. Disfrute de la finalizaci\u00f3n de c\u00f3digo m\u00e1s inteligente, las comprobaciones de conformidad PEP 8, las refactorizaciones inteligentes y una gran variedad de inspecciones para satisfacer todas sus necesidades de codificaci\u00f3n. Como se demuestra en este art\u00edculo, PyCharm ofrece ajustes personalizados para los int\u00e9rpretes de Python y las configuraciones de ejecuci\u00f3n, lo que le permite cambiar entre int\u00e9rpretes con solo unos clics, por lo que es adecuado para una amplia gama de proyectos de Python.<\/p>\n<div class=\"buttons\">\n<div class=\"buttons__row\">\n                                                <a href=\"https:\/\/www.jetbrains.com\/pycharm\/\" class=\"btn\" target=\"\" rel=\"noopener\">Descargue PyCharm ahora<\/a>\n                                                    <\/div>\n<\/p>\n<\/div>\n\n\n<p>A<em>rt\u00edculo original en ingl\u00e9s de:<\/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":593146,"comment_status":"closed","ping_status":"closed","template":"","categories":[1401,2347],"tags":[8056,5377],"cross-post-tag":[8851],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/593110"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/types\/pycharm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/users\/1086"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/comments?post=593110"}],"version-history":[{"count":6,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/593110\/revisions"}],"predecessor-version":[{"id":650290,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/593110\/revisions\/650290"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/media\/593146"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/media?parent=593110"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/categories?post=593110"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/tags?post=593110"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/cross-post-tag?post=593110"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}