Conflits d’écriture entre threads (malgré le GIL)

a marqué ce sujet comme résolu.
Auteur du sujet

Bonjour,

Dans le cadre de ce tutoriel, j’ai voulu expliquer l’intérêt de l’exclusion mutuelle entre threads en Python. J’ai donc écrit ce bout de code :

import time
from threading import Thread

N = 1000000
i = 0


class IncrThread(Thread):
    def run(self):
        global i
        # On effectue plus d'un seul incrément pour augmenter la probabilité
        # de conflit entre les threads
        for _ in range(N):
            i += 1


def run(thread):
    global i
    i = 0
    t1 = thread()
    t2 = thread()
    start = time.time()
    # Les méthodes `t1.run()` et `t2.run()` sont exécutées en parallèle. Elles vont chacune
    # incrémenter `i` en parallèle.
    t1.start()
    t2.start()
    # On attend que `t1` et `t2` terminent, c'est-à-dire qu'ils aient chacun
    # incrémenté `i` `N` fois.
    t1.join()
    t2.join()
    end = time.time()
    # Comme `i` a en théorie été incrémentée `N` fois par chacun des threads,
    # on s'attend à ce qu'elle soit égale à `2N`.
    print('{0:.2f}'.format(end-start), 'secondes, 2N-i =', 2*N-i)


if __name__ == '__main__':
    print('Sans exclusion mutuelle :')
    run(IncrThread)

Il me donne :

Sans exclusion mutuelle :
0.27 secondes, 2N-i = 709582

Et quand on ajoute des verrous :

import time
from threading import Thread, Lock

N = 1000000
i = 0


class LockedIncrThread(Thread):
    def __init__(self, lock):
        super().__init__()
        self.lock = lock

    def run(self):
        global i
        for _ in range(N):
            # On utilise le verrou le moins longtemps possible pour ne pas bloquer
            # excessivement les autres threads. C'est pourquoi la ressource est
            # réservée dans la boucle et non pas à l'extérieur.
            with self.lock:
                i += 1

# ...

if __name__ == '__main__':
    print('Avec exclusion mutuelle :')
    # On crée un seul verrou pour tous les threads
    lock = Lock()
    run(lambda: LockedIncrThread(lock))

On obtient :

Avec exclusion mutuelle :
3.85 secondes, 2N-i = 0

J’ai été surpris parce qu’il me semblait que, justement, le GIL en Python empêchait de faire de la vraie concurrence avec des threads et que poser des verrous était inutiles.

Qu’ai-je manqué ?

Merci.


Édité par Angelo

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte