3
\$\begingroup\$

I wrote a simple class in Python, which controls code invocation, in a multi-threaded environment, with the following logic:

The class' main method, named try_to_do, takes two function pointers as arguments: yes_we_can_fn and no_we_cannot_fn.

In any point in time, only a single thread can invoke code the function passed as yes_we_can_fn argument.

If a specific thread tries to invoke its code, but some other thread is already invoking its code, then the no_we_cannot_fn is invoked instead of yes_we_can_fn.

If there's an exception in the code being executed, it should raise to the calling context.

The code:

from threading import Lock

class MyController():
    def __init__(self):
        self.locker = Lock()
        self.is_active = False

    def try_to_do(self, yes_we_can_fn, no_we_cannot_fn):
        with self.locker:
            if self.is_active:
                we_can_do_it = False
            else:
                we_can_do_it = True
                self.is_active = True

        try:
            if we_can_do_it:
                yes_we_can_fn()
            else:
                no_we_cannot_fn()
        finally:
            if we_can_do_it:
                with self.locker:
                    self.is_active = False

Usage:

ctl = MyController()

def p1():
    from time import sleep
    print 'before sleep'
    sleep(2)
    print 'done'

def p2():
    print 'too busy, will try some other time'

ctl.try_to_do(p1, p2)

I'd like to get some reviews: thread safety (maybe I'm missing something?), coding style, etc.

\$\endgroup\$
2
\$\begingroup\$

You could avoid the is_active variable by using the lock in a non-blocking manner:

def try_to_do(self, yes_we_can_fn, no_we_cannot_fn):
    if self.locker.acquire(False):
        try:
            yes_we_can_fn()
        finally:
            self.locker.release()
    else:
        no_we_cannot_fn()
\$\endgroup\$
  • \$\begingroup\$ That's a great idea. Note that there should also be a try/finally around the no_we_cannot_fn(). \$\endgroup\$ – Ron Klein Jan 20 '14 at 19:04
  • \$\begingroup\$ @RonKlein In your version the finally block does nothing in the case where no_we_cannot_fn() gets called. \$\endgroup\$ – Janne Karila Jan 20 '14 at 19:09
  • \$\begingroup\$ After another look in your suggested code, I see that even if there was as exception while invoking no_we_cannot_fn, the class' state won't get affected. In my (original) code, it would be. So you're absolutely right, there's no need for a try wrapper around no_we_cannot_fn() in your suggestion. \$\endgroup\$ – Ron Klein Jan 20 '14 at 21:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.