web-dev-qa-db-fra.com

Python Threading à l'intérieur d'une classe

J'ai récemment commencé avec le module de threading de python. Après quelques essais et erreurs, j'ai réussi à faire fonctionner le filetage de base en utilisant l'exemple de code suivant donné dans la plupart des didacticiels.

class SomeThread(threading.Thread):
    def __init__(self, count):
        threading.Thread.__init__(self)

    def run(self):
        print "Do something"

Mon problème est: j'ai une classe qui a des variables de classe et une fonction que je veux exécuter dans un thread séparé. Cependant, la fonction utilise des variables de classe et écrit également dans des variables de classe. Ainsi:

class MyClass:
    somevar = 'someval'

    def func_to_be_threaded(self):
        # Uses other class functions
        # Do something with class variables

Alors, comment pourrais-je essentiellement "mettre la classe de threads dans MyClass". Ainsi, si MyClass (). Func_to_threaded () est appelé, il s'exécutera dans un thread.

33
Niels

Si je comprends bien, vous souhaitez exécuter une fonction dans un thread séparé? Il y a plusieurs façons de procéder. Mais en gros, vous enveloppez votre fonction comme ceci:

class MyClass:
    somevar = 'someval'

    def _func_to_be_threaded(self):
        # main body

    def func_to_be_threaded(self):
        threading.Thread(target=self._func_to_be_threaded).start()

Il peut être raccourci avec un décorateur:

def threaded(fn):
    def wrapper(*args, **kwargs):
        threading.Thread(target=fn, args=args, kwargs=kwargs).start()
    return wrapper

class MyClass:
    somevar = 'someval'

    @threaded
    def func_to_be_threaded(self):
        # main body

Modifier Version mise à jour avec un handle:

def threaded(fn):
    def wrapper(*args, **kwargs):
        thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
        thread.start()
        return thread
    return wrapper

class MyClass:
    somevar = 'someval'

    @threaded
    def func_to_be_threaded(self):
        print 'xyz'

Cela peut être utilisé comme suit:

>>> my_obj = MyClass()
>>> handle = my_obj.func_to_be_threaded()
>>> handle.join()

Il est maintenant possible de l'étendre encore plus si vous souhaitez renvoyer une valeur de la fonction. Considère ceci:

from threading import Thread
from concurrent.futures import Future

def call_with_future(fn, future, args, kwargs):
    try:
        result = fn(*args, **kwargs)
        future.set_result(result)
    except Exception as exc:
        future.set_exception(exc)

def threaded(fn):
    def wrapper(*args, **kwargs):
        future = Future()
        Thread(target=call_with_future, args=(fn, future, args, kwargs)).start()
        return future
    return wrapper


class MyClass:
    @threaded
    def get_my_value(self):
        return 1

>>> my_obj = MyClass()
>>> fut = my_obj.get_my_value()  # this will run in a separate thread
>>> fut.result()  # will block until result is computed
1

Si vous n'avez pas classe concurrent.futures.Future (parce que, par exemple, vous utilisez Python2.7 ou une version antérieure), vous pouvez utiliser cette implémentation simplifiée:

from threading import Event

class Future(object):
    def __init__(self):
        self._ev = Event()

    def set_result(self, result):
        self._result = result
        self._ev.set()

    def set_exception(self, exc):
        self._exc = exc
        self._ev.set()

    def result(self):
        self._ev.wait()
        if hasattr(self, '_exc'):
            raise self._exc
        return self._result

Je conseille de lire le module concurrent.futures car il a beaucoup d'outils soignés. Par exemple, la classe Thread doit être remplacée par une instance ThreadPoolExecutor pour limiter la concurrence (par exemple, vous ne voulez pas spammer des threads 10k). De plus, avec ThreadPoolExecutor, le code est encore plus simple (et moins sujet aux erreurs):

from concurrent.futures import ThreadPoolExecutor

tp = ThreadPoolExecutor(10)  # max 10 threads

def threaded(fn):
    def wrapper(*args, **kwargs):
        return tp.submit(fn, *args, **kwargs)  # returns Future object
    return wrapper

N'oubliez pas que vous devez tp.shutdown() après avoir terminé tout le travail en parallèle.

84
freakish

Vous pouvez passer l'instance de classe au thread:

class SomeThread(threading.Thread):
    def __init__(self, count, instance):
        threading.Thread.__init__(self)
        self.instance = instance

    def run(self):
        print "Do something"
        self.instance.some_param = data
        self.instance.some_function()
4
ndpu