Metaclasses, decorators, and synchronization

Scott David Daniels scott.daniels at acm.org
Mon Sep 26 17:21:12 EDT 2005


Michael Ekstrand wrote:
> Something like this (not yet tested):
> 
> import threading
> global_lock = threading.Lock()
> def synchronized(meth):
>     def inner(self, *args, **kwargs):
>         try:
>             self._sync_lock.acquire()
>         except AttributeError:
>             global_lock.acquire()
>             if not hasattr(self, '_sync_lock'):
>                 self._sync_lock = threading.RLock()
>             self._sync_lock.acquire()
>             global_lock.release()
>         meth(self, *args, **kwargs)
>         self._sync_lock.release()
>     return inner
> I don't think this solution has any race conditions (hence the re-check 
> of the existence of _sync_lock), and I've tried to optimize it for the 
> common case in my project (calling the method after the lock has been 
> put in place - that will happen much more than a call to a synchronized 
> method of a fresh object).

Better would probably be:

 >         ...
 >         except AttributeError:
 >             global_lock.acquire()
 >             if not hasattr(self, '_sync_lock'):
 >                 self._sync_lock = threading.RLock()
 >             global_lock.release()  # release first
 >             self._sync_lock.acquire()
 >         ...
or even:
 > def synchronized(meth):
 >     def inner(self, *args, **kwargs):
 >         try:
 >             self._sync_lock.acquire()
 >         except AttributeError:
 >             global_lock.acquire()
 >             if not hasattr(self, '_sync_lock'):
 >                 try:
 >                     self._sync_lock = threading.RLock()
 >                 finally:
 >                     global_lock.release()  # release first
 >             self._sync_lock.acquire()
 >         try:
 >             meth(self, *args, **kwargs)
 >         finally:
 >             self._sync_lock.release()
 >     return inner

Unnecessarily holding a lock while acquiring another can be a nasty
source of deadlock or at least delay.  Another source of problems is
holding a lock because an exception skipped past the release code.

Imagine in your original code:
 > A        try:
 > B            self._sync_lock.acquire()
 > C        except AttributeError:
 > D            global_lock.acquire()
 > E            if not hasattr(self, '_sync_lock'):
 > F                self._sync_lock = threading.RLock()
 > G            self._sync_lock.acquire()
 > H            global_lock.release()
 > I        meth(self, *args, **kwargs)
 > J        self._sync_lock.release()

Thread one executes:
     o = TheClass()
     v = o.themethod()  # and executes: A, B, C, D, E, F
         # Now thread1 holds only global_lock
Then Thread two executes:
     w = o.themethod()  # and executes: A, B, I
         # now thread 2 holds o._sync_lock

Thread one cannot proceed (it needs o._sync_lock) until Thread two
completes its code.  If, for example, the method body in Thread two
calls an unrelated synchronized method (perhaps on another object)
and must create another _sync_lock, Threads one and two will be
deadlocked.


--Scott David Daniels
scott.daniels at acm.org



More information about the Python-list mailing list