Erreur interne en postant un message

a marqué ce sujet comme résolu.

En voulant poster un message dans le forum Programmation (il y a une minute environ), je me retrouve avec une erreur interne (à deux reprises et le message ne se poste pas). Voici le message en question :


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

Tags : thread, python

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.


Merci.

+0 -0

Salut,

Je ne sais pas pour ton erreur interne du site, mais le comportement que tu décris en Python est normal.

Le GIL n’empêche pas la concurrence, il empêche le parallélisme. Deux opérations ne seront jamais exécutées simultanément.
Ça n’empêche pas que l’environnement est ici concurrent, et que l’exécution d’un fil peut être suspendue à tout moment (enfin, après toute opération) pour être remplacée par un autre.

Ici, simplement, l’opération i += 1 n’est pas atomique. Le fil d’exécution peut-être suspendu entre le moment où il calcule i + 1 et celui où il réassigne cette valeur.

Par ailleurs je ne vois pas bien le besoin de redéclarer et redéfinir i dans la fonction run.

Merci pour la réponse.

Par ailleurs je ne vois pas bien le besoin de redéclarer et redéfinir i dans la fonction run.

Parce qu’à l’origine, les deux classes sont dans le même fichier. Donc après la première expérience, i vaut 709582 et non plus 0.

+0 -0

L’erreur se reproduit avec ce message :


Bonjour,

Je travaille avec Python 3.5+ et j’ai une architecture de classes de ce genre :

class Dataset:
    def read(self):
        raise NotImplementedError()

class JSONDataset(Dataset):
    def read(self):
        return [1, 2, 3]

class Type1Dataset(Dataset): pass

class JSONType1Dataset(Type1Dataset, JSONDataset): pass

class Type2Dataset(Dataset): pass

class JSONType2Dataset(Type2Dataset, JSONDataset):
    def process_data(self, data):
        return reverse(data)

    def read(self):
        data = JSONDataset.read(self)
        return self.process_data(data)

Il s’avère que JSONType2Dataset.process_data() est la même fonction pour tous les Type2Datasets. Plutôt donc que d’avoir cela, je me demandais s’il existait un moyen de surcharger la méthode read() directement dans Type2Dataset.

class JSONType2Dataset(Type2Dataset, JSONDataset):
    def process_data(self, data):
        return reverse(data)

    def read(self):
        data = JSONDataset.read(self)
        return self.process_data(data)

class JSONType2Dataset(Type2Dataset, XMLDataset):
    def process_data(self, data):
        return reverse(data)

    def read(self):
        data = XMLDataset.read(self)
        return self.process_data(data)

Merci.


+0 -0

Il s’avère que JSONType2Dataset.process_data() est la même fonction pour tous les Type2Datasets. Plutôt donc que d’avoir cela, je me demandais s’il existait un moyen de surcharger la méthode read() directement dans Type2Dataset.

Vayel

Oui, et c’est tout le principe de super : éviter d’avoir à écrire explicitement le nom du parent.

J’imagine que si plusieurs parents exposent la même méthode, il me faut trancher manuellement ?

Je me permets de poster une autre question ici vu que l’erreur 500 apparait toujours (je créerai des sujets pour les questions une fois le problème réglé) :


Bonjour,

Ca fait quelque temps que j’utilise Python mais je n’ai jamais appris à écrire du bon Python. Je respecte au mieux la PEP-08 mais c’est tout.

Je me demandais donc si vous auriez des conseils pour apprendre à écrire du bon code en Python (outre les règles qui s’appliquent à tous les langages comme bien nommer ses variables). En particulier :

  • Y a-t-il du code (particulièrement bien écrit) que je devrais lire ?
  • Comment me familiariser avec la bibliothèque standard autrement qu’en lisant simplement la documentation (ce qui n’est pas très stimulant) ?

Merci.

+0 -0

J’imagine que si plusieurs parents exposent la même méthode, il me faut trancher manuellement ?

Vayel

Non, c’est le MRO qui gère ça. Les ascendants d’une classe sont linéarisés, de façon à ce que super renvoie vers la classe précédente. Si toutes les redéfinitions de méthodes utilisent super, ça se passe sans problème.

  • Y a-t-il du code (particulièrement bien écrit) que je devrais lire ?
Vayel

Oui : http://sametmax.com/exemples-de-bons-codes-python/

  • Comment me familiariser avec la bibliothèque standard autrement qu’en lisant simplement la documentation (ce qui n’est pas très stimulant) ?
Vayel

En étant confronté aux problèmes auxquels répondent ces modules.

Si tu fais beaucoup de choses combinatoires par exemple (c’est courant dans les problèmes type projecteuler, tu seras amené à te renseigner sur itertools).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

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