Python a reçu beaucoup d'attention ces derniers temps. La version 3.13, prévue pour octobre de cette année, lancera l'énorme travail de suppression du GIL. Une avant-première est déjà disponible pour les utilisateurs curieux qui souhaitent essayer un Python (presque) sans GIL.
Tout ce battage médiatique m'a fait creuser dans mon propre langage, ArkScript, car j'avais aussi un Global VM Lock dans le passé (ajouté dans la version 3.0.12, en 2020, supprimé dans la version 3.1.3 en 2022), pour comparer les choses et me forcer à approfondir le comment et le pourquoi du Python GIL.
Un verrou d'interpréteur global (GIL) est un mécanisme utilisé dans les interpréteurs de langage informatique pour synchroniser l'exécution des threads afin qu'un seul thread natif (par processus) puisse exécuter des opérations de base (telles que l'allocation de mémoire et le comptage de références) à la fois. temps.
Wikipédia — Verrouillage global de l'interprète
La concurrence se produit lorsque deux tâches ou plus peuvent démarrer, s'exécuter et se terminer dans des périodes de temps qui se chevauchent, mais cela ne signifie pas qu'elles s'exécuteront toutes les deux simultanément.
Le parallélisme se produit lorsque les tâches s'exécutent littéralement en même temps, par exemple sur un processeur multicœur.
Pour une explication détaillée, consultez cette réponse Stack Overflow.
Le GIL peut augmenter la vitesse des programmes monothread car vous n'avez pas besoin d'acquérir et de libérer des verrous sur toutes les structures de données : l'intégralité de l'interpréteur est verrouillée, vous êtes donc en sécurité par défaut.
Cependant, comme il y a un GIL par interpréteur, cela limite le parallélisme : vous devez générer un tout nouvel interpréteur dans un processus séparé (en utilisant le module multitraitement au lieu du threading) pour utiliser plus d'un cœur ! Cela coûte plus cher que la simple création d'un nouveau thread, car vous devez maintenant vous soucier de la communication inter-processus, ce qui ajoute une surcharge non négligeable (voir GeekPython — GIL devient facultatif dans Python 3.13 pour les benchmarks).
Dans le cas de Python, cela tient au fait que l'implémentation principale, CPython, ne dispose pas d'une gestion de la mémoire thread-safe. Sans le GIL, le scénario suivant générerait une condition de concurrence :
Si le thread 1 s'exécute en premier, le compte sera 11 (compte * 2 = 10, puis compte + 1 = 11).
Si le thread 2 s'exécute en premier, le compte sera 12 (compte + 1 = 6, puis compte * 2 = 12).
L'ordre d'exécution est important, mais pire encore peut arriver : si les deux threads lisent le compte en même temps, l'un effacera le résultat de l'autre, et le compte sera soit 10, soit 6 !
Dans l'ensemble, avoir un GIL rend la mise en œuvre (CPython) plus facile et plus rapide dans les cas généraux :
Cela facilite également l'encapsulation des bibliothèques C, car la sécurité des threads est garantie grâce au GIL.
L'inconvénient est que votre code est asynchrone comme en concurrent, mais pas parallèle.
[!NOTE]
Python 3.13 supprime le GIL !Le PEP 703 a ajouté une configuration de construction --disable-gil afin que lors de l'installation de Python 3.13+, vous puissiez bénéficier d'améliorations de performances dans les programmes multithread.
En Python, les fonctions doivent prendre une couleur : elles sont soit "normales", soit "async". Qu'est-ce que cela signifie en pratique ?
>>> def foo(call_me): ... print(call_me()) ... >>> async def a_bar(): ... return 5 ... >>> def bar(): ... return 6 ... >>> foo(a_bar) <coroutine object a_bar at 0x10491f480> <stdin>:2: RuntimeWarning: coroutine 'a_bar' was never awaited RuntimeWarning: Enable tracemalloc to get the object allocation traceback >>> foo(bar) 6
Étant donné qu'une fonction asynchrone ne renvoie pas de valeur immédiatement, mais appelle plutôt une coroutine, nous ne pouvons pas les utiliser partout comme rappels, à moins que la fonction que nous appelons ne soit conçue pour accepter des rappels asynchrones.
Nous obtenons une hiérarchie de fonctions, car les fonctions "normales" doivent être rendues asynchrones pour utiliser le mot-clé wait, nécessaire pour appeler des fonctions asynchrones :
can call normal -----------> normal can call async -+-----------> normal | .-----------> async
En dehors de faire confiance à l'appelant, il n'y a aucun moyen de savoir si un rappel est asynchrone ou non (à moins que vous n'essayiez d'abord de l'appeler dans un bloc try/sauf pour vérifier une exception, mais c'est moche).
In the beginning, ArkScript was using a Global VM Lock (akin to Python's GIL), because the http.arkm module (used to create HTTP servers) was multithreaded and it caused problems with ArkScript's VM by altering its state through modifying variables and calling functions on multiple threads.
Then in 2021, I started working on a new model to handle the VM state so that we could parallelize it easily, and wrote an article about it. It was later implemented by the end of 2021, and the Global VM Lock was removed.
ArkScript does not assign a color to async functions, because they do not exist in the language: you either have a function or a closure, and both can call each other without any additional syntax (a closure is a poor man object, in this language: a function holding a mutable state).
Any function can be made async at the call site (instead of declaration):
(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<0, 0x0x7f0e84d85dd0> (print (await future)) # 6 (print (await future)) # nil
Using the async builtin, we are spawning a std::future under the hood (leveraging std::async and threads) to run our function given a set of arguments. Then we can call await (another builtin) and get a result whenever we want, which will block the current VM thread until the function returns.
Thus, it is possible to await from any function, and from any thread.
All of this is possible because we have a single VM that operates on a state contained inside an Ark::internal::ExecutionContext, which is tied to a single thread. The VM is shared between the threads, not the contexts!
.---> thread 0, context 0 | ^ VM <----+ can't interact | v .---> thread 1, context 1
When creating a future by using async, we are:
This forbids any sort of synchronization between threads since ArkScript does not expose references or any kind of lock that could be shared (this was done for simplicity reasons, as the language aims to be somewhat minimalist but still usable).
However this approach isn't better (nor worse) than Python's, as we create a new thread per call, and the number of threads per CPU is limited, which is a bit costly. Luckily I don't see that as problem to tackle, as one should never create hundreds or thousands of threads simultaneously nor call hundreds or thousands of async Python functions simultaneously: both would result in a huge slow down of your program.
In the first case, this would slowdown your process (even computer) as the OS is juggling to give time to every thread ; in the second case it is Python's scheduler that would have to juggle between all of your coroutines.
[!NOTE]
Out of the box, ArkScript does not provide mechanisms for thread synchronization, but even if we pass a UserType (which is a wrapper on top of type-erased C++ objects) to a function, the underlying object isn't copied.
With some careful coding, one could create a lock using the UserType construct, that would allow synchronization between threads.(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)Copier après la connexion
ArkScript and Python use two very different kinds of async / await: the first one requires the use of async at the call site and spawns a new thread with its own context, while the latter requires the programmer to mark functions as async to be able to use await, and those async functions are coroutines, running in the same thread as the interpreter.
Originally from lexp.lt
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!