8
\$\begingroup\$

I needed to impose on a Python method the following locking semantics: the method can only be run by one thread at a time. If thread B tries to run the method while it is already being run by thread A, then thread B immediately receives a return value of None.

I wrote the following decorator to apply these semantics:

from threading import Lock

def non_blocking_lock(fn):
    fn.lock = Lock()

    @wraps(fn)
    def locker(*args, **kwargs):
        if fn.lock.acquire(False):
            try:
                return fn(*args, **kwargs)
            finally:
                fn.lock.release()

    return locker

This works in my testing so far. Does anyone notice any gotchas or have any suggestions for improvements?

Revised version

After the suggestions by @RemcoGerlich, I have added a docstring and kept the lock local to the decorator:

from threading import Lock

def non_blocking_lock(fn):
    """Decorator. Prevents the function from being called multiple times simultaneously.

    If thread A is executing the function and thread B attempts to call the
    function, thread B will immediately receive a return value of None instead.

    """
    lock = Lock()

    @wraps(fn)
    def locker(*args, **kwargs):
        if lock.acquire(False):
            try:
                return fn(*args, **kwargs)
            finally:
                lock.release()

    return locker
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

I think this will work fine and it's very close to how I would write it myself.

A few small things come to mind:

  • There's no documentation of any kind that explains what the semantics are, and they're not explicit either (the return None if the lock isn't acquired is entirely implicit). I would put the short explanation you put in this question into a docstring, and/or add an explicit else: return None to the if statement.

  • is there any reason why the lock object is exposed to the outside world by making it a property of the function (fn.lock) ? I would simply make it a local variable, so that it's hidden. But I'm not sure.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your feedback! I’ve amended my original code and added it to the question, for posterity. \$\endgroup\$
    – bdesham
    Commented Feb 26, 2014 at 14:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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