Seeking advice on locking iterators
Gonçalo Rodrigues
op73418 at mail.telepac.pt
Fri Nov 15 10:33:07 EST 2002
On Fri, 15 Nov 2002 15:12:15 +0000, Gonçalo Rodrigues
<op73418 at mail.telepac.pt> wrote:
>On Thu, 14 Nov 2002 19:29:49 +0100, Ype Kingma <ykingma at accessforall.nl>
>wrote:
>
>>Gonçalo Rodrigues schreef:
>>
>>> Hi,
>>>
>>> My problem is the following: I have a top object Application, roughly
>>> corresponding to the main thread, that spawns some child threads -
>>> objects on their own sake, deriving from the Thread class in the
>>> threading module. They communicate when they need to via queues in the
>>> usual way. So far so nice.
>>>
>>> The problem is that those same child objects expose some public
>>> attributes (or properties, or methods, does not matter) that the
>>> Application object may need to change/call and doing the change via the
>>> queue mechanism is conceptually wrong (and a real PITA). The solution is
>>> obvious: wrap the attributes with locks around it. What is not so
>>> obvious is when these attributes are complex objects themselves, in
>>> particular they are iterables. In order to call iter() on these objects
>>> safely I coded the following helper class:
>>>
>>> #WARNING: Brittle - use this at your own risk.
>>> class TIter(object):
>>> """The TIter helper class, wrapping an iterator for thread safe
>>> acess."""
>>>
>>> def __init__(self, lock, iterator):
>>> super(TIter, self).__init__(lock, iterator)
>>> self.__lock = lock
>>> self.__iterator = iter(iterator)
>>> #Acquire lock => You cannot change the underlying structure
>>> #while iter not exhausted.
>>> self.__lock.acquire()
>>> self.__acquired = 1
>>>
>>> #Iterator protocol.
>>> def __iter__(self):
>>> return self
>>>
>>> def next(self):
>>> try:
>>> ret = self.__iterator.next()
>>> except StopIteration:
>>> self.__lock.release()
>>> self.__acquired = 0
>>> raise StopIteration
>>> else:
>>> return ret
>>>
>>> def __del__(self):
>>> if self.__acquired:
>>> self.__lock.release()
>>>
>>> The idea is that for the wrapping of a given attribute one has an
>>> associated lock (a reentrant lock, usually) that is passed to the ITer
>>> constructor. I have to ensure two things, though:
>>>
>>> * The lock is acquired while the iterator is alive.
>>> * The lock is released when the iterator is disposed of.
>>>
>>> So, finally, my question is: Is the above enough to ensure this? Can I
>>
>>In general, no. Some reasons:
>>One can never be sure from the code shown when the __del__ method will be
>>called, since this depends on when the last reference to the iterator goes
>>out of scope.
>>Although CPython more or less guarantees that the __del__ method will be
>>called quite soon after the last reference goes out of scope, the language
>>by itself does not make such a guarantee. In Jython, you are at the mercy
>>of a separate a garbage collecting thread in the JVM to call the __del__
>>method, ie. it might take more than a few million CPU cycles before the
>>__del__ method is called.
>>
>>> be sure that __del__ is called if the iterator is garbage collected
>>> before being exhausted? Or is there a better (e.g. safer) mechanism for
>>> this?
>>
>>
>>One safe way is:
>>
>>yourObject.lock()
>>try:
>> # use an iterator on yourObject.
>>finally:
>> yourObject.unlock()
>> # and don't use the iterator again.
>>
>>Ie. I don't see the point of locking the iterator, as you seem to want to
>>lock an object, ie. one the attributes you mentioned above.
>>
>
>I want to lock the iterator because I want the wrapped object to have
>the same interface as the object it wraps.
>
>>The lock() and unlock() methods need to be added to the class of yourObject
>>for this to work.
>
>Anyway I think I have found a solution: The problem lies in the fact
>that the object I am iterating over and the iterator itself can be
>different objects => while the iterator acquires the lock it may get
>garbage collected before it releases it. Therefore I just wrap the
>object in a way that the wrapper is its own iterator and the problem
>disappears.
>>
Actually the problem does not disappear completely since the iterator
itself may still get confused if some thread in the middle of some
iterating decides to alter the structure. A bit like the
altering-the-structure-of-an-iterable-while-iterating-over-it problem.
And for *that reason* I do have to add lock/unlock methods. Oh well.
Just goes to show the importance of the Queue module when dealing with
threads...
>>Have fun,
>
>I am. Python *is* fun.
>
>>Ype
>
>With my best regards,
>G. Rodrigues
With my best regards,
G. Rodrigues
More information about the Python-list
mailing list