.. _gilexamplerst: ====== Le GIL ====== .. only:: html **Links:** :download:`notebook `, :downloadlink:`html `, :download:`PDF `, :download:`python `, :downloadlink:`slides `, :githublink:`GitHub|_doc/notebooks/python/gil_example.ipynb|*` Le GIL ou `Global Interpreter Lock `__ est un verrou unique auquel l’interpréteur Python fait appel constamment pour protéger tous les objets qu’il manipule contre des accès concurrentiels. .. code:: ipython3 from jyquickhelper import add_notebook_menu add_notebook_menu() .. contents:: :local: Deux listes en parallèlle ------------------------- On mesure le temps nécessaire pour créer deux liste et comparer ce temps avec celui que cela prendrait en parallèle. .. code:: ipython3 def create_list(n): res = [] for i in range(n): res.append(i) return res %timeit create_list(100000) .. parsed-literal:: 10.4 ms ± 1.87 ms per loop (mean ± std. dev. of 7 runs, 100 loops each) En parallèle avec le module `concurrent.futures `__ et deux appels à la même fonction. .. code:: ipython3 from concurrent.futures import ThreadPoolExecutor def run2(nb): with ThreadPoolExecutor(max_workers=2) as executor: for res in executor.map(create_list, [nb, nb+1]): pass %timeit run2(100000) .. parsed-literal:: 54.7 ms ± 4.94 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) C’est plus long que si les calculs étaient lancés les uns après les autres. Ce temps est perdu à synchroniser les deux threads bien que les deux boucles n’aient rien à échanger. Chaque thread passe son temps à attendre que l’autre ait terminé de mettre à jour sa liste et le *GIL* impose que ces mises à jour aient lieu une après l’autre. Un autre scénario ----------------- Au lieu de mettre à jour une liste, on va lancer un thread qui ne fait rien qu’attendre. Donc le *GIL* n’est pas impliqué. .. code:: ipython3 import time def attendre(t=0.009): time.sleep(t) return None %timeit attendre() .. parsed-literal:: 9.36 ms ± 28.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) .. code:: ipython3 def run2(t): with ThreadPoolExecutor(max_workers=2) as executor: for res in executor.map(attendre, [t, t+0.001]): pass %timeit run2(0.009) .. parsed-literal:: 12.6 ms ± 43.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) Les deux attentes se font en parallèle car le temps moyen est significativement inférieur à la somme des deux attentes.