"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Comparación de modelos asincrónicos de Python y ArkScript

Comparación de modelos asincrónicos de Python y ArkScript

Publicado el 2024-11-08
Navegar:964

Comparing Python and ArkScript asynchronous models

Python ha recibido mucha atención últimamente. La versión 3.13, prevista para octubre de este año, iniciará el enorme trabajo de eliminar el GIL. Ya está disponible una versión preliminar para los usuarios curiosos que quieran probar un Python (casi) sin GIL.

Todo este revuelo me hizo profundizar en mi propio lenguaje, ArkScript, ya que también tenía un Global VM Lock en el pasado (agregado en la versión 3.0.12, en 2020, eliminado en 3.1.3 en 2022), para compara cosas y me obliga a profundizar en el cómo y el por qué de Python GIL.

Definiciones

  1. Para comenzar, definamos qué es un GIL (Bloqueo global de intérprete):

Un bloqueo de intérprete global (GIL) es un mecanismo utilizado en intérpretes de lenguajes informáticos para sincronizar la ejecución de subprocesos de modo que solo un subproceso nativo (por proceso) pueda ejecutar operaciones básicas (como asignación de memoria y recuento de referencias) a la vez. tiempo.

Wikipedia: bloqueo global del intérprete

  1. Simultaneidad es cuando dos o más tareas pueden iniciarse, ejecutarse y completarse en períodos de tiempo superpuestos, pero eso no significa que ambas se ejecutarán simultáneamente.

  2. Paralelismo es cuando las tareas se ejecutan literalmente al mismo tiempo, por ejemplo, en un procesador multinúcleo.

Para obtener una explicación detallada, consulte esta respuesta de Stack Overflow.

GIL de Python

El GIL puede aumentar la velocidad de los programas de un solo subproceso porque no es necesario adquirir y liberar bloqueos en todas las estructuras de datos: todo el intérprete está bloqueado, por lo que usted está seguro de forma predeterminada.

Sin embargo, dado que hay un GIL por intérprete, eso limita el paralelismo: ¡necesita generar un intérprete completamente nuevo en un proceso separado (usando el módulo de multiprocesamiento en lugar de subprocesos) para usar más de un núcleo! Esto tiene un costo mayor que simplemente generar un nuevo hilo porque ahora tiene que preocuparse por la comunicación entre procesos, lo que agrega una sobrecarga no despreciable (consulte GeekPython: GIL se vuelve opcional en Python 3.13 para obtener puntos de referencia).

¿Cómo afecta la asincronía de Python?

En el caso de Python, se debe a que la implementación principal, CPython, no tiene administración de memoria segura para subprocesos. Sin GIL, el siguiente escenario generaría una condición de carrera:

  1. crear un recuento de variables compartidas = 5
  2. hilo 1: recuento *= 2
  3. hilo 2: recuento = 1

Si el hilo 1 se ejecuta primero, el recuento será 11 (recuento * 2 = 10, luego recuento 1 = 11).

Si el hilo 2 se ejecuta primero, el recuento será 12 (recuento 1 = 6, luego recuento * 2 = 12).

El orden de ejecución importa, pero puede suceder algo peor: si ambos subprocesos leen el conteo al mismo tiempo, uno borrará el resultado del otro y el conteo será 10 o 6.

En general, tener un GIL hace que la implementación (CPython) sea más fácil y rápida en casos generales:

  • más rápido en el caso de un solo subproceso (no es necesario adquirir/liberar un bloqueo para cada operación)
  • más rápido en el caso de subprocesos múltiples para programas vinculados a IO (porque ocurren fuera del GIL)
  • más rápido en el caso de subprocesos múltiples para programas vinculados a la CPU que realizan su trabajo de computación intensiva en C (porque el GIL se publica antes de llamar al código C)

También facilita el empaquetado de bibliotecas C, porque tienes garantizada la seguridad de los subprocesos gracias a GIL.

La desventaja es que tu código es asincrónico como en concurrente, pero no paralelo.

[!NOTA]
¡Python 3.13 está eliminando el GIL!

El PEP 703 agregó una configuración de compilación --disable-gil para que al instalar Python 3.13, puedas beneficiarte de mejoras de rendimiento en programas multiproceso.

Modelo asíncrono/en espera de Python

En Python, las funciones deben tomar un color: son "normales" o "asíncronas". ¿Qué significa esto en la práctica?

>>> def foo(call_me):
...     print(call_me())
... 
>>> async def a_bar():
...     return 5
... 
>>> def bar():
...     return 6
... 
>>> foo(a_bar)
:2: RuntimeWarning: coroutine 'a_bar' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
>>> foo(bar)
6

Debido a que una función asincrónica no devuelve un valor inmediatamente, sino que invoca una corrutina, no podemos usarlas en todas partes como devoluciones de llamada, a menos que la función que estamos llamando esté diseñada para aceptar devoluciones de llamada asíncronas.

Obtenemos una jerarquía de funciones, porque las funciones "normales" deben hacerse asíncronas para usar la palabra clave await, necesaria para llamar a funciones asíncronas:

         can call
normal -----------> normal

         can call
async - -----------> normal
       |
       .-----------> async                    

Aparte de confiar en la persona que llama, no hay forma de saber si una devolución de llamada es asíncrona o no (a menos que intentes llamarla primero dentro de un bloque try/except para comprobar si hay una excepción, pero eso es feo).

Paralelismo de ArkScript

Al principio, ArkScript usaba un bloqueo global de VM (similar al GIL de Python), porque el módulo http.arkm (usado para crear servidores HTTP) era multiproceso y causaba problemas con la VM de ArkScript al alterar su estado mediante la modificación de variables. y llamar a funciones en múltiples subprocesos.

Luego, en 2021, comencé a trabajar en un nuevo modelo para manejar el estado de la VM para que pudiéramos paralelizarlo fácilmente y escribí un artículo al respecto. Posteriormente se implementó a fines de 2021 y se eliminó el bloqueo global de VM.

ArkScript asíncrono/espera

ArkScript no asigna un color a las funciones asíncronas, porque no existen en el lenguaje: o tienes una función o un cierre, y ambos pueden llamarse entre sí sin ninguna sintaxis adicional (un cierre es un objeto pobre, en este idioma: una función que mantiene un estado mutable).

Cualquier función se puede hacer asincrónica en el sitio de llamada (en lugar de declaración):

(let foo (fun (a b c)
    (  a b c)))

(print (foo 1 2 3))  # 6

(let future (async foo 1 2 3))
(print future)          # UserType
(print (await future))  # 6
(print (await future))  # nil

Usando el async incorporado, estamos generando un std::future bajo el capó (aprovechando std::async y threads) para ejecutar nuestra función dado un conjunto de argumentos. Luego podemos llamar a await (otra función incorporada) y obtener un resultado cuando queramos, lo que bloqueará el subproceso actual de la VM hasta que la función regrese.

Por lo tanto, es posible esperar desde cualquier función y desde cualquier hilo.

Las especificidades

Todo esto es posible porque tenemos una única VM que opera en un estado contenido dentro de un Ark::internal::ExecutionContext, que está vinculado a un solo hilo. ¡La VM se comparte entre los subprocesos, no los contextos!

        .---> thread 0, context 0
        |            ^
VM  thread 1, context 1              

Al crear un futuro usando async, somos:

  1. copiando todos los argumentos al nuevo contexto,
  2. creando una pila y alcances completamente nuevos,
  3. finalmente crea un hilo separado.

Esto prohíbe cualquier tipo de sincronización entre subprocesos ya que ArkScript no expone referencias ni ningún tipo de bloqueo que pueda compartirse (esto se hizo por razones de simplicidad, ya que el lenguaje pretende ser algo minimalista pero aún utilizable).

Sin embargo, este enfoque no es mejor (ni peor) que el de Python, ya que creamos un nuevo subproceso por llamada y la cantidad de subprocesos por CPU es limitada, lo cual es un poco costoso. Afortunadamente, no veo eso como un problema que deba abordarse, ya que nunca se deben crear cientos o miles de subprocesos simultáneamente ni llamar a cientos o miles de funciones asíncronas de Python simultáneamente: ambos resultarían en una enorme desaceleración de su programa.

En el primer caso, esto ralentizaría su proceso (incluso la computadora), ya que el sistema operativo hace malabarismos para darle tiempo a cada hilo; en el segundo caso, es el programador de Python el que tendría que hacer malabares entre todas sus rutinas.

[!NOTA]
Fuera de la caja, ArkScript no proporciona mecanismos para la sincronización de subprocesos, pero incluso si pasamos un tipo de usuario (que es un contenedor encima de objetos C borrados de tipo ) a una función, el objeto subyacente no es No se copió.

Con un poco de codificación cuidadosa, se podría crear un bloqueo utilizando la construcción UserType, que permitiría la sincronización entre subprocesos.

(let lock (module:createLock))
(let foo (fun (lock i) {
  (lock true)
  (print (str:format "hello {}" i))
  (lock false) }))
(async foo lock 1)
(async foo lock 2)

Conclusión

ArkScript y Python usan dos tipos muy diferentes de async/await: el primero requiere el uso de async en el sitio de la llamada y genera un nuevo hilo con su propio contexto, mientras que el segundo requiere que el programador marque las funciones como asíncronas para podrá usar await, y esas funciones asíncronas son corrutinas que se ejecutan en el mismo hilo que el intérprete.

Fuentes

  1. Stack Exchange: ¿Por qué se escribió Python con GIL?
  2. Python Wiki: GlobalInterpreterLock
  3. stuffwithstuff - ¿De qué color es tu función?

Originalmente de leexp.lt

Declaración de liberación Este artículo se reproduce en: https://dev.to/lexplt/comparing-python-and-arkscript-asynchronous-models-3l60?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarla.
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3