Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

creating a thread safe decorator class

I've written a class that I'm using as a decorator. What I've done clearly isn't thread safe, but needs to be.

Here's a very simple example:

class DecoratorClass(object):
    def __init__(self):
        print 'initing DecoratorClass'
        self.counter = 0

    def __call__(self, func, *args, **kwargs):

        def wrapped_func(*args, **kwargs):
            print '\nin wrapped func, func name:', func.func_name
            print 'count val:', self.counter
            self.counter += 1
            ret_val = func()
            return ret_val

        return wrapped_func

@DecoratorClass()
def decorated_with_class():
    return

decorated_with_class()
decorated_with_class()
decorated_with_class()

This is the output:

initing DecoratorClass

in wrapped func, func name: decorated_with_class
count val: 0

in wrapped func, func name: decorated_with_class
count val: 1

in wrapped func, func name: decorated_with_class
count val: 2

So I'm getting one instance of DecoratorClass, which is shared between the three functions that have been decorated with it. Is there a straightforward way to get a new DecoratorClass instance on each call of decorated_with_class()? Or some other way to make this thread safe (e.g. have self.counter begin at 0 each time)? Or is this a lost cause?

Edits for clarifiation:

  • I've intentionally omitted any threading in this example for the sake of keeping things simple; the environment in which I'm working WILL be threaded, but this example is not.

  • I'm not actually counting anything in my production code. My real issue is that I have an instance variable on my decorator class, which may be updated by two instances of the same function each running in different threads. I just used the counter example to show that the decorator class only gets one instance, which maintains state. I need each of the threads/functions to either 1. get their own instance of the decorator class or 2. wait until no other threads are using the decorator class.

like image 966
almaghest Avatar asked Nov 07 '25 16:11

almaghest


1 Answers

Ok, here's a better example of what I was running into:

import threading
import time

class DecoratorClass(object):
    def __init__(self):
        self.thread = None

    def __call__(self, func, *args, **kwargs):
        def wrapped_func(*args, **kwargs):
            curr_thread = threading.currentThread().getName()
            self.thread = curr_thread
            print '\nthread name before running func:', self.thread
            ret_val = func()
            print '\nthread name after running func:', self.thread
            return ret_val

        return wrapped_func

@DecoratorClass()
def decorated_with_class():
    print 'running decorated w class'
    time.sleep(1)
    return

threads = []
for i in range(5):
    t = threading.Thread(target=decorated_with_class)
    threads.append(t)
    t.start()

This outputs:

thread name before running func: Thread-1
running decorated w class

thread name before running func: Thread-2

thread name before running func: running decorated w classThread-3
thread name before running func:

running decorated w class

thread name before running func:Thread-5

running decorated w class
Thread-5
running decorated w class

thread name after running func: Thread-5

thread name after running func: Thread-5

thread name after running func: Thread-5

thread name after running func: Thread-5

thread name after running func: Thread-5

So, clearly all the threads are using the same instance of DecoratorClass, and self.thread ends up being Thread-5 by the time all of them are done running (rather than being the name of the thread that actually ran the code.)

I just needed to add a lock into my decorator, like so:

class DecoratorClass(object):
    def __init__(self):
        self.thread = None
        self.lock = threading.Lock()

    def __call__(self, func, *args, **kwargs):
        def wrapped_func(*args, **kwargs):

            self.lock.acquire()

            curr_thread = threading.currentThread().getName()
            self.thread = curr_thread

            print '\nthread name before running func:', self.thread
            ret_val = func()
            print '\nthread name after running func:', self.thread
            self.lock.release()
            return ret_val

        return wrapped_func

@DecoratorClass()
def decorated_with_class():
    print 'running decorated w class'
    time.sleep(1)
    return

threads = []
for i in range(5):
    t = threading.Thread(target=decorated_with_class)
    threads.append(t)
    t.start()

Now my output looks like this, which is what I want:

thread name before running func: Thread-1
running decorated w class

thread name after running func: Thread-1

thread name before running func: Thread-2
running decorated w class

thread name after running func: Thread-2

thread name before running func: Thread-3
running decorated w class

thread name after running func: Thread-3

thread name before running func: Thread-4
running decorated w class

thread name after running func: Thread-4

thread name before running func: Thread-5
running decorated w class

thread name after running func: Thread-5
like image 195
almaghest Avatar answered Nov 10 '25 07:11

almaghest



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!