IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Threads & Lock


Sujet :

Python

  1. #41
    Membre éprouvé

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Points : 1 273
    Points
    1 273
    Par défaut
    Et pourquoi ça paraît logique ?
    Parce que le binding python/dll doit forcément contenir quelque part un handle (adresse mémoire) sur le code réel de la dll, dans l’espace d’adressage du processus courant. Et comme chaque processus à son propre espace d’adressage (virtuel évidemment), géré par l’OS, il y a fort à parier que la mémoire correspondant à la DLL dans le processus A ne soit du tout la même que celle pour le processus B (je sais pas si je suis super clair, là…). Donc même si ctype.CDLL était sérialisable, il est à mon avis quasi-certain que ça ne marcherait pas du tout (erreurs de segmentation en perspective ).

    Donc, à mon avis, il est impossible de partager des données internes à une DLL entre divers processus ! (sauf peut-être si la DLL est conçue pour ça, mais même là, je doute… beaucoup ! Ça me semblerait être une faille de sécurité assez monstrueuse !).

    Pour ton compteur, le mieux serait d’utiliser, comme suggéré par tyrtamos, une multiprocessing.Value, qui d’après la doc (http://docs.python.org/py3k/library/...ocessing.Value), est mappée en mémoire partagée avec en prime un verrou, et donc accessible par tous les processus, en toute sécurité.

  2. #42
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,

    Je vois que votre code multiprocessing devient plus ambitieux. En passant, je vous signale que comme tout le monde essaie de faire un peu la même chose que vous i.e. lancer des activités gérées par un pool de workers et récupérer leurs résultats, Python 3.2 vient avec une bibliothèque concurrent.futures qui fait déjà tout çà.
    En gros, votre code se réduirait à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    import multiprocessing as mp
    from multiprocessing import Lock, Manager
    from concurrent import futures
    from concurrent.futures import ALL_COMPLETED
     
    class Console(object):
        def __init__(self, lock=None):
            self._lock = lock if lock else Lock()
     
        def __call__(self, s):
            with self._lock:
                print (s)
     
    def activity(msg, count, print=print):
        p = mp.current_process()
        for c in range(count):
            print("name: %s, %s, %d" % (p.name, msg, c))
        return msg[0]
     
    if __name__ == '__main__':
     
        todo_list = ["XXX", "III", "///", ":::", "000", "+++", "###", "%%%", "***", "^^^", "$$$"]
     
        manager = Manager()
        console = Console(manager.Lock())
        max_workers = mp.cpu_count()
     
        with futures.ProcessPoolExecutor(
               max_workers=max_workers) as executor:
            flist = []
            for todo in todo_list:
                flist.append(
                    executor.submit(
                        activity, todo, 2, print=console))
            else:
                futures.wait(flist,
                              return_when=ALL_COMPLETED)
            for r in flist:
                print (r.result())
    Note: j'ai ajouté l'engin Manager pour gérer la ressource partagée Lock, car je n'ai pas réussi à faire fonctionner cela "normalement".

    Ça veut dire que dans la situation réelle de mon projet, je dois refaire tout un paquet de réinitialisations 4 fois, recharger un fichier qui peut être gros, alors que ce n'est pas vraiment utile.
    C'est un défaut de votre design: coller threads et activités signifie initialiser un process pour chaque activité et quand c'est un process "fat" comme sous Windows, cet alliage coûte d'autant plus que l'initialisation est longue pour une durée de vie est "courte".

    Citation Envoyé par mont29
    Donc, à mon avis, il est impossible de partager des données internes à une DLL entre divers processus ! (sauf peut-être si la DLL est conçue pour ça, mais même là, je doute… beaucoup ! Ça me semblerait être une faille de sécurité assez monstrueuse !).
    On se calme et on revient aux bases de chez "base".

    Il était une fois l'espace virtuel que peut adresser le process. Cet espace est construit à son lancement en initialisant des pages de tables de pages. Cette chose décrit "potentiellement" chaque page de l'espace virtuel pour savoir quoi y mettre lorsque l'exécution du process lit/écrit une adresse dans la page en question.

    La primitive OS essentielle qui permet de construire ces descripteurs est mmap. mmap permet de mapper un fichier dans l'espace virtuel du process, i.e. le descripteur dit: va chercher la page dans le fichier.

    Exécutables et DLL ne sont pas lues mais mappées via la mécanique mmap.

    mmap n'interdit pas que certaines zone de deux processus correspondent aux mêmes "objets", c'est la mécanique de "shared memory" qui est réalisée via un mapping vers les mêmes pages physiques ou vers le même backing store (fichier de pagination ou fichier quelconque).

    Avec ce truc, on intuite que les DLL peuvent être partagées de façon différentes:
    • Chaque process construit sa copie, simple et basique mais bof.
    • On déclare certaines DLL partagées et lorsqu'un process a besoin de charger la DLL, l'OS lui retourne une copie de l'espace qu'il a déjà chargé.

    Deux modèles: copy-on-write ou shared.
    • copy-on-write est similaire au mécanisme de "fork" unix: seule les pages modifiées sont privées les autres sont communes.
    • "shared" est le mécanisme de mémoire partagé "classique" construit avec mmap.


    Appliqué à une DLL, cela suppose pouvoir définir des "zones" pour préciser ce qui est partagé - tout ne peut pas l'être parce qu'instable! -.

    La construction de l'espace virtuel se faisant au chargement... il faut que le chargeur puisse savoir comment construire via mmap les zones mémoire.

    Ce qui suppose avoir donné quelques mots magiques à l'édition de liens... et donc de forte dépendances avec l'OS, le linker, l'éditeur de liens... rendant cette facilité peu répandue.

    Mais rien n'empêche la DLL de construire elle même sa zone partagée via mmap et d'en gérer la synchronisation d'accès via les locks et autres sémaphores proposés par l'OS....

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #43
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Décembre 2009
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Décembre 2009
    Messages : 42
    Points : 17
    Points
    17
    Par défaut
    .
    Citation Envoyé par FredOoo123 Voir le message
    .
    Une version simplifiée, améliorée et commentée de MultiCore.py (pour Python 2.6.6, 2.7.1 et 3.2)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    # -*- coding: utf_8 -*-
     
    import multiprocessing as mp
    import sys
     
    __all__ = ["MultiProcess", "CoreProcess"]
     
     
    if ( sys.version[0]=="3" ):   #: En fait, j'utilise partout (enfin, seulement deux fois) des multiprocessing.Queue()
        from queue import Empty   #: mais l'exception qui dit que la Queue est vide est celle de la Queue standard,
    else:                         #: laquelle se trouve dans le module 'Queue', ou 'queue' dans les versions 3.x de Python
        from Queue import Empty
     
     
     
    class CConsole (object):
        # Vérrouille les écritures dans la console
     
        def __init__(self):
            self._lock = mp.Lock()
     
        def __call__( self, txt ):
            with self._lock:
                print( txt )
     
     
     
    class MultiProcess (object):
        # Lance un nombre de Process identique au nombre de CPU sur la machine pour effectuer tous les traitements dans la liste
        # ( Le plus important à comprendre, c'est les deux mp.Queue : ToDoQueue et ResultQueue
        def __init__( self, CProcess, ToDoList, **DArgs ):
            self.CProcess    = CProcess
            self.ToDoQueue   = mp.Queue()  #: Et non plus = CToDoQueue( ToDoListe )
            for item in ToDoList:             #: Ces deux lignes remplacent la class CToDoQueue présentes dans
                self.ToDoQueue.put(item)      #:   les versions précédentes du module
            self.Console     = CConsole()
            self.NbrProcess  = min( mp.cpu_count(), len(ToDoList) ) #: Car on ne va quand-même pas lancer plus de Process qu'il y a d'ItemToDo.
            self.Process     = []
            self.ResultQueue = mp.Queue()
            self.DArgs = DArgs
     
        def __call__( self ):
            self.start()
            self.join()
            #: Ici, tous les travaux sont fini, on s'occupe de ranger les résultats
            ResultList = []
            QIsNotEmpty = True
            while QIsNotEmpty:
                try:
                    Result = self.ResultQueue.get_nowait()
                    ResultList += [Result]
                except Empty: #: Quand la liste est vide, ça retourne une erreur
                    QIsNotEmpty = False
                except Exception: #: Là, c'est pas normal!
                    raise
            return ResultList # Dans n'importe quel ordre
     
        def start( self ):
            for i in range( self.NbrProcess ):
                self.Process.append ( self.CProcess( self.ResultQueue , self.Console, self.ToDoQueue, **self.DArgs ) )
                self.Process[i].start()
     
        def join( self ):
            for i in range( self.NbrProcess ):
                self.Process[i].join()
     
     
     
    class CoreProcess ( mp.Process ):
        # Processus qui fait sa part de boulot tant qu'il en reste dans la liste
        # ( Dans mon projet réel, l'initialisation du Process peut être lourde, c'est pourquoi
        # ( il n'est pas initialisé autant de fois qu'il y a d'éléments à traiter mais seulement
        # ( autant de fois qu'il y a de CPU
        # Interface :
        #    run()           : Doit être surchargé
        #    getItemToDo()   : Obtenir une tâche à exécuter
        #    addItemResult() : Ajouter un résultat
        #    Empty           : Exception quand la liste des tâches est vide
        #    Console( txt )  : Pour faire un print verrouillé
        #    Et aussi tout argument nommé passé à MultiProcess
     
        def __init__( self, ResultQueue, Console, ToDoQueue, **DArgs ):
            # Si surchargé il faut faire : super (MyCoreProcess,self)__init__(self, *TArgs, **DArgs)
            super( CoreProcess, self ).__init__()
            self.ResultQueue = ResultQueue
            self.Console     = Console
            self.ToDoQueue   = ToDoQueue
            self.Empty       = Empty               #: Comme ça, l'utilisateur qui hérite de CoreProcess n'est pas emmerdé avec
            for attr, value in DArgs.items():      #: la déclaration Queue.Empty ou queue.Empty
                dict.__setattr__( self, attr, value )     #: Permet à tous les arguments nommés de DArgs, par exemple toto=33
                                                          #: d'être accessibles dans run() par self.toto
        def run( self ):
            # Devra être surchargé pour faire vraiment ce que l'on veut
            import time, os
            QIsNotEmpty = True
            while QIsNotEmpty:
                try:
                    # ////////////////////////////////////////////////
                    ItemToDo = self.getItemToDo()
                    time.sleep(0.2)
                    self.Console( "%s %s" % (ItemToDo, self.toto) )
                    self.addItemResult( ItemToDo*ItemToDo )
                    # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
                except self.Empty:
                    QIsNotEmpty = False
                except Exception:
                    raise
     
        def getItemToDo( self ):
            return self.ToDoQueue.get_nowait()
     
        def addItemResult( self, ItemResult ):
            self.ResultQueue.put( ItemResult )
     
     
     
    if __name__ == '__main__':
     
        # Initialisations
        ToDoList = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
        MyMultiProcess = MultiProcess( CoreProcess, ToDoList, toto=33 )
     
        # Ici je fais le job, dans des Process, avec les start() et les joint()
        ResultList = MyMultiProcess()
     
        print('')
     
        # Ici je fais ce que je veux avec mes résultats
        for Result in ResultList:
            print( Result )
     
        print('')
    La simplification c'est qu'il n'y a plus de class CToDoQueue.

    L'amélioration c'est que l'on peut passer autant de d'arguments nommés au constructeur de MultiProcess (voir **DArgs, par exemple toto=33), on les retrouvera sous forme d'attributs dans CoreProcess (exemple self.toto a pour valeur 33).

    Bien sûr, il vaut mieux faire ceci : (J'ai modifié de code dans la partie citée)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
            self.NbrProcess  = min( multi.cpu_count(), len(ToDoList) )
    à la ligne 37. Car on ne va quand-même pas lancer plus de Process qu'il y a d'ItemToDo.


    Édition:
    Wiztricks, je vois que vous n'êtes pas revenu les mains vides. Et comme d'habitude, il va falloir que je réfléchisse à tout ça.

    En attendant, ce vous propose ceci à la ligne 26 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    max_workers = min( mp.cpu_count(), len(todo_list) )

    L'ambition ?:
    C'était celle-ci depuis le début. Mon projet. J'ai décortiqué au fur et à mesure.

    Le code que vous proposez :
    Ça ne m'étonne pas pas que tout le monde veuille faire la même chose. La majorité n'a pas toujours raison, mais elle se retrouve souvent quand c'est du bon sens. J'espère que c'est le cas...
    Ce code est intéressant et je vais y réfléchir de près, d'autant plus que vous avez pris la peine de le faire.
    Pour mon projet actuel, je suis quand-même dépendant d'un côté de la DLL, de l'autre d'un paquet de code en Python 2.6.6. Dans mon projet, je ne peux donc pas passer à la version future de 3.2, mais si je peux comprendre l'esprit de de ce future, je peux améliorer ma solution.

    Les explication sur les DLL :
    Oui, on a toujours tort de perdre de vue les fondamentaux. Mais la pratique nous oblige souvent à fondre sur des solutions toutes faites qui marchent tout de suite. Sur ce chapitre, il va falloir que je révise un peu.
    Merci pour les pistes.

    .

  4. #44
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Salut,
    Pour mon projet actuel, je suis quand-même dépendant d'un côté de la DLL, de l'autre d'un paquet de code en Python 2.6.6. Dans mon projet, je ne peux donc pas passer à la version future de 3.2, mais si je peux comprendre l'esprit de de ce future, je peux améliorer ma solution.
    Vous avez un backport de python concurrent futures pour les versions de python antérieures (jusqu'à 2.5) disponible sur PyPi
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #45
    Membre à l'essai
    Homme Profil pro
    Inscrit en
    Décembre 2009
    Messages
    42
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Décembre 2009
    Messages : 42
    Points : 17
    Points
    17

  6. #46
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 283
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 283
    Points : 36 770
    Points
    36 770
    Par défaut
    Joli code n'est-il pas?
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

+ Répondre à la discussion
Cette discussion est résolue.
Page 3 sur 3 PremièrePremière 123

Discussions similaires

  1. [Multi-thread] Comment lock l'acces a un containeur de la STL ?
    Par Izidor's dans le forum Threads & Processus
    Réponses: 5
    Dernier message: 14/10/2009, 12h09
  2. Thread et lock pour écrire dans un fichier?
    Par DarkHerumor dans le forum C#
    Réponses: 3
    Dernier message: 31/03/2009, 09h40
  3. Réponses: 5
    Dernier message: 14/08/2008, 11h25
  4. comment utiliser les lock dans les threads ?
    Par skad dans le forum Général Python
    Réponses: 2
    Dernier message: 15/07/2008, 14h28
  5. Thread et lock de tables
    Par babylone7 dans le forum Oracle
    Réponses: 6
    Dernier message: 27/06/2006, 17h31

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo