1

I am trying to implement thread safe code but encounter some simple problem. I searched and not found solution.

Let me show abstract code to describe problem:

import threading

class A(object):
  sharedLock = threading.Lock()
  shared = 0

  @classmethod
  def getIncremented(cls):
    with cls.sharedLock:
      cls.shared += 1
      return cls.shared

class B(A):
  pass

class C(A):
  @classmethod
  def getIncremented(cls):
    with cls.sharedLock:
      cls.shared += B.getIncremented()
      return cls.shared

I want to define class A to inherit many child classes for example for enumerations or lazy variables - specific use no matter. I am already done single thread version now want update multi thread.

This code will give such results as should do:

id(A.sharedLock) 11694384
id(B.sharedLock) 11694384
id(C.sharedLock) 11694384

I means that lock in class A is lock in class B so it is bad since first entry into class B will lock also class A and class C. If C will use B it will lead to dedlock.

I can use RLock but it is invalid programming pattern and not sure if it not produce more serious deadlock.

How can I change sharedLock value during initialization of class to new lock to make id(A.sharedLock) != id(B.sharedLock) and same for A and C and B and C?

How can I hook class initialization in python in generic to change some class variables?

That question is not too complex but I do not know what to do with it.

Chameleon
  • 9,722
  • 16
  • 65
  • 127
  • 1
    If `A` and `C` had different locks, then incrementing `cls.shared` would not be thread-safe. – unutbu Apr 21 '14 at 11:54
  • [Your comments confuse me](http://stackoverflow.com/questions/23196437/how-to-avoid-deadlock-with-class-attributes-initialization-with-locks-inheritan/23197711#comment35482531_23197711). Let's simplify: imagine you have a variable with a thread-safe `increment()` method i.e., there is no explicit separate lock variable. Do you want each subclass to have its own such variable? – jfs Apr 21 '14 at 13:15
  • I will response in answer thread. – Chameleon Apr 21 '14 at 14:35

3 Answers3

3

I want inherit parent share variables except shared parent locks

You must not do this. It makes access to "share variables" not thread-safe.


sharedLock protects shared variable. If the same shared variable can be modified in a recursive call then you need RLock(). Here shared means shared among all subclasses.

It looks like you want a standalone function (or a static method) instead of the classmethod:

def getIncremented(_lock=Lock(), _shared=[0]):
    with _lock:
      _shared[0] += 1
      return _shared[0]

Thus all classes use the same shared variable (and the corresponding lock).

If you want each class to have its own shared variable (here shared means shared among instances of this particular class) then don't use cls.shared that may traverse ancestors to get it.

To hint that subclasses shouldn't use a variable directly, you could use the syntax for a private variable:

class A:
   __shared = 0
   __lock = Lock()

If a subclass overrides a method that uses __shared then it won't use A.__shared by accident in the code directly.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • I want use classmethod (need to have cls to access members). You approach is interesting with function initializers and suggestion with RLock is clear. You skip some important thing/assumption that I want design base class which allow derive children classes with unique locks - if lock will same in all derived classes it will not works. – Chameleon Apr 21 '14 at 12:56
  • @Chameleon: The design is wrong. **you must use the same lock** otherwise access to `shared` variable won't be thread-safe – jfs Apr 21 '14 at 12:58
  • We mean **different things** talking about **shared** - you are right in your meaning context - lock is need per variable (or set of variables) for thread safe. Problem is that **derived lock** from **parent** to **child** is shared so it lead to deadlocks by invalid design. Important is how to not allow initialize child class attribute with parent attribute lock - as it will be done with `class child(parent): pass`? – Chameleon Apr 21 '14 at 13:06
  • Consider that I have `CachedEnumeration` and want build many enumeration with use of this class and critical is loading enumeration before caching - I do not want load all not need data at program creation (often 99% can be not used at specific request) and program is multi threads. – Chameleon Apr 21 '14 at 13:10
  • I have parent class which have **shared class variables** - this class will be not used to create objects. I want inherit children classes from this class which **inherit shared class variable** - this classes will be not used to create objects too. Since children **inherit shared class variable** I need to have lock per children classes not per one parent class - so question is how to create such lock with simple inheritance without patching children? – Chameleon Apr 21 '14 at 14:40
  • Make it simpler I want **inherit parent share variables** except **shared parent locks** which should be reinitialize probably by metaclass or other python feature. If I will **inherit locks values** I will kill multi threading and make deadlock high possible. – Chameleon Apr 21 '14 at 14:42
  • @Chameleon: I don't see how your comments correlates with my assertion: [*"imagine ...there is no explicit separate lock variable"*](http://stackoverflow.com/questions/23196437/how-to-avoid-deadlock-with-class-attributes-initialization-with-locks-inheritan/23197711?noredirect=1#comment35482789_23196437). – jfs Apr 21 '14 at 16:48
  • I explained it - more directly to you comment: Yes, I need separate lock per each class derived from parent. Now I think that it can be done only with replace metaclass with my own initialing locks. – Chameleon Apr 22 '14 at 11:16
  • @Chameleon: You are on the wrong path. I don't know how to say it more simply: "no *explicit* lock" means that you shouldn't care that it exits. Imagine that `shared` is a `Semaphore`-like object: it has `acquire()/release()` methods (like `decrement()/increment()`), it may use a lock internally but it is not visible to you, all you could do is to call `decrement()/increment()` methods. – jfs Apr 22 '14 at 11:42
  • I put simple and clear solution at bottom - if something wrong please comment - I tested it and works nice. Each class has own lock so there will be no deadlocks if it will be not used to create objects. – Chameleon Apr 22 '14 at 15:55
1

As you noticed, if you expose shared locks as class attributes, the locks are shared by subclasses.

You could hack around this by redefining the lock on each subclass:

class B(A):
  sharedLock = threading.Lock()

You could even use metaclasses to achieve this (please don't). It seems to me that you're approaching the program from the wrong angle.

This task is easier if you assign locks explicitly to instances (not classes).

class A(object):
    def __init__(self, lock):
        this.sharedLock= lock

my_lock= threading.Lock()
a= A(my_lock)

Of course, you run into the "problem" of having to explicitly pass the lock for each instance. This is traditionally solved using a factory pattern, but in python you can simply use functions properly:

from functools import partial
A_with_mylock= partial(A, my_lock)
a2= A_with_mylock()
loopbackbee
  • 21,962
  • 10
  • 62
  • 97
  • Good suggestions but not sufficient. First workaround need to remember every time to update lock that is pattern leading to serious problems. Second pattern is valid but can not be used I am using currently classes not instance so can not use it. Need to study third since not know `partial` at all. – Chameleon Apr 21 '14 at 11:44
  • It is there some other method with use i.e. metaclass? – Chameleon Apr 21 '14 at 11:44
  • The whole point of this answer (and J.F. Sebastian's) is that maybe you *shouldn't* be using classes and class methods for the task. Maybe you could edit the question to mention what you're trying to accomplish? As I mentioned, you can use a metaclass to create a one-lock-per-class architecture. Not being familiar with the underlying problem, my best advice is to [check this metaclass tutorial](http://stackoverflow.com/a/6581949/1595865), in order to understand the subtleties involved. – loopbackbee Apr 21 '14 at 13:32
  • I found some solution with use of metaclass thank you for hints. I replace default metaclass with my own and initializing `lock` per each class - now inheritance is possible and there is no deadlocks. Nothing wrong with use classes and class attributes but it need to change behavior - it is equal to object instances. Python is truly object oriented so all is object and not problem with flexibility and productivity. – Chameleon Apr 22 '14 at 11:21
  • 1
    @Chameleon I'm glad you found the info you needed. Feel free to post your solution as an answer, so that the community may benefit from it. – loopbackbee Apr 22 '14 at 12:03
0

Here is solution - this allow separate lock per each class since it done on class constructor level (metaclass). Thank you for all hints and help to achieve this code it looks very nice.

I can be also mangled variable but need to use hardcode '_A__lock' what can be problematic and not tested by me.

import threading

class MetaA(type):
  def __new__(self, name, bases, clsDict):
    # change <type> behavior
    clsDict['_lock'] = threading.Lock()
    return super(MetaA, self).__new__(self, name, bases, clsDict)

class A(object):
  __metaclass__ = MetaA

  @classmethod
  def getLock(cls):
    return cls._lock

class B(A):
  pass

print 'id(A.getLock())', id(A.getLock())
print 'id(B.getLock())', id(B.getLock())
print A.getLock() == B.getLock()
Chameleon
  • 9,722
  • 16
  • 65
  • 127
  • Your solution tries to create a separate lock per class. If you are trying to use them to protect some `shared` variable common to *all* classes then it is wrong. It is not how locks work. Creating a separate lock only makes sense if you also create `shared` variable per class (a *single* lock per shared variable). – jfs Apr 22 '14 at 22:36
  • Yes, it will work for **variables per class** it will not work for **variables per object instance** - object will need __init__ code. – Chameleon Apr 24 '14 at 08:57
  • I haven't mention instances of a class (`C()` if `C` is a class) in my comment. – jfs Apr 24 '14 at 09:02