2010-05-05 3 views
1

Ceci est probablement une question rudimentaire, mais je suis novice en programmation Python et je ne suis pas entièrement sûr de la pratique correcte.Implémentation de verrouillage Python (avec module de thread)

Dois-je créer un seul objet de verrouillage (globalement ou en cours de transmission) et l'utiliser partout où j'ai besoin de verrouiller? Ou, devrais-je créer plusieurs instances de verrouillage dans chacune des classes où je vais les employer. Prenez ces 2 échantillons de code rudimentaire, quelle direction est la meilleure pour aller? La principale différence étant qu'une seule instance de verrou est utilisée dans les deux classes A et B dans la seconde, alors que plusieurs instances sont utilisées dans la première.

Exemple 1

class A(): 
    def __init__(self, theList): 
     self.theList = theList 
     self.lock = threading.Lock() 

    def poll(self): 
     while True: 
      # do some stuff that eventually needs to work with theList 
      self.lock.acquire() 
      try: 
       self.theList.append(something) 
      finally: 
       self.lock.release() 





class B(threading.Thread): 
    def __init__(self,theList): 
     self.theList = theList 
     self.lock = threading.Lock() 
     self.start() 


    def run(self): 
     while True: 
      # do some stuff that eventually needs to work with theList 
      self.lock.acquire() 
      try: 
       self.theList.remove(something) 
      finally: 
       self.lock.release() 



if __name__ == "__main__": 
    aList = [] 
    for x in range(10): 
     B(aList) 

    A(aList).poll() 

Exemple 2

class A(): 
    def __init__(self, theList,lock): 
     self.theList = theList 
     self.lock = lock 

    def poll(self): 
     while True: 
      # do some stuff that eventually needs to work with theList 
      self.lock.acquire() 
      try: 
       self.theList.append(something) 
      finally: 
       self.lock.release() 



class B(threading.Thread): 
    def __init__(self,theList,lock): 
     self.theList = theList 
     self.lock = lock 
     self.start() 


    def run(self): 
     while True: 
      # do some stuff that eventually needs to work with theList 
      self.lock.acquire() 
      try: 
       self.theList.remove(something) 
      finally: 
       self.lock.release() 



if __name__ == "__main__": 
    lock = threading.Lock() 
    aList = [] 
    for x in range(10): 
     B(aList,lock) 

    A(aList,lock).poll() 

Répondre

8

Si vous utilisez un objet de verrouillage distinct dans chaque classe, vous risquez de provoquer un blocage, par ex. si une opération revendique le verrou pour A et revendique le verrou pour B alors qu'une opération différente réclame B et ensuite A.

Si vous utilisez un verrou unique, vous forcez le code à thread unique lorsque différentes opérations peuvent être exécutées en parallèle. Ce n'est pas toujours aussi grave en Python (qui a un verrou global dans tous les cas) que dans les autres langages, mais disons que vous deviez garder un verrou global en écrivant dans un fichier Python libérerait le GIL mais vous auriez tout bloqué autre.

Donc c'est un compromis. Je dirais opter pour de petits verrous car ainsi vous maximisez les chances d'une exécution parallèle, mais prenez soin de ne jamais réclamer plus d'un verrou à la fois, et essayez de ne pas garder un verrou plus longtemps que nécessaire.

En ce qui concerne vos exemples spécifiques, le premier est tout simplement cassé. Si vous verrouillez les opérations sur theList, vous devez utiliser le même verrou à chaque fois ou vous ne verrouillez rien.Cela n'a peut-être pas d'importance ici car list.append et list.remove sont effectivement atomiques de toute façon, mais si vous avez besoin de verrouiller l'accès à la liste, vous devez être sûr d'utiliser le même verrou à chaque fois. Le meilleur moyen de le faire est de maintenir la liste et un verrou en tant qu'attributs d'une classe et de forcer tous les accès à la liste à passer par les méthodes de la classe contenant. Passez ensuite la classe de conteneur autour de la liste ou du verrou.

+0

Merci! C'est exactement l'aide que je cherchais! – Matty

7

Dans le cas général, un seul verrou global est moins efficace (plus contention), mais plus sûr (aucun risque de blocage) tant qu'il est un RLock (réentrant) plutôt qu'un plaine Lock.

Les problèmes potentiels surviennent lorsqu'un thread qui exécute un verrou tente d'acquérir un autre verrou (ou le même), par exemple en appelant une autre méthode qui contient l'appel acquire. Si un fil qui est déjà titulaire d'un verrou tente de l'acquérir à nouveau, il bloque toujours si le verrou est une Lock simple, mais bon déroulement si c'est un RLock un peu plus complexe - c'est la raison pour laquelle celui-ci est appelé rentrante, parce que le fil le tenant peut "entrer" (acquérir le verrou) à nouveau. Essentiellement, un RLock garde la trace de , ce que le thread contient, et combien de fois le thread a acquis le verrou, tandis que le verrou plus simple ne conserve pas ces informations. Avec plusieurs verrous, le problème d'interblocage survient lorsqu'un thread essaye d'acquérir le verrou A puis le verrouille B, tandis qu'un autre essaie d'acquérir le premier verrou B, puis le verrou A. Si cela se produit, tôt ou tard, vous serez dans une situation où le premier verrou détient A, le second verrouille B, et chacun essaye d'acquérir le verrou que l'autre maintient - donc les deux bloquent pour toujours. Une manière d'empêcher les blocages multiples est de s'assurer que les verrous sont toujours acquis dans le même ordre, quel que soit le thread qui fait l'acquisition. Cependant, lorsque chaque instance a son propre verrou, c'est extrêmement difficile à organiser avec clarté et simplicité.

+0

offtopic Félicitations pour votre 100k !!! .. – OscarRyz

+0

@Oscar, merci! -) –

Questions connexes