mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	svn+ssh://pythondev@svn.python.org/python/branches/py3k
................
  r76119 | antoine.pitrou | 2009-11-05 14:49:14 +0100 (jeu., 05 nov. 2009) | 10 lines
  Merged revisions 76117 via svnmerge from
  svn+ssh://pythondev@svn.python.org/python/trunk
  ........
    r76117 | antoine.pitrou | 2009-11-05 14:42:29 +0100 (jeu., 05 nov. 2009) | 5 lines
    Issue #7264: Fix a possible deadlock when deallocating thread-local objects
    which are part of a reference cycle.
  ........
................
		
	
			
		
			
				
	
	
		
			242 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Thread-local objects.
 | 
						|
 | 
						|
(Note that this module provides a Python version of the threading.local
 | 
						|
 class.  Depending on the version of Python you're using, there may be a
 | 
						|
 faster one available.  You should always import the `local` class from
 | 
						|
 `threading`.)
 | 
						|
 | 
						|
Thread-local objects support the management of thread-local data.
 | 
						|
If you have data that you want to be local to a thread, simply create
 | 
						|
a thread-local object and use its attributes:
 | 
						|
 | 
						|
  >>> mydata = local()
 | 
						|
  >>> mydata.number = 42
 | 
						|
  >>> mydata.number
 | 
						|
  42
 | 
						|
 | 
						|
You can also access the local-object's dictionary:
 | 
						|
 | 
						|
  >>> mydata.__dict__
 | 
						|
  {'number': 42}
 | 
						|
  >>> mydata.__dict__.setdefault('widgets', [])
 | 
						|
  []
 | 
						|
  >>> mydata.widgets
 | 
						|
  []
 | 
						|
 | 
						|
What's important about thread-local objects is that their data are
 | 
						|
local to a thread. If we access the data in a different thread:
 | 
						|
 | 
						|
  >>> log = []
 | 
						|
  >>> def f():
 | 
						|
  ...     items = sorted(mydata.__dict__.items())
 | 
						|
  ...     log.append(items)
 | 
						|
  ...     mydata.number = 11
 | 
						|
  ...     log.append(mydata.number)
 | 
						|
 | 
						|
  >>> import threading
 | 
						|
  >>> thread = threading.Thread(target=f)
 | 
						|
  >>> thread.start()
 | 
						|
  >>> thread.join()
 | 
						|
  >>> log
 | 
						|
  [[], 11]
 | 
						|
 | 
						|
we get different data.  Furthermore, changes made in the other thread
 | 
						|
don't affect data seen in this thread:
 | 
						|
 | 
						|
  >>> mydata.number
 | 
						|
  42
 | 
						|
 | 
						|
Of course, values you get from a local object, including a __dict__
 | 
						|
attribute, are for whatever thread was current at the time the
 | 
						|
attribute was read.  For that reason, you generally don't want to save
 | 
						|
these values across threads, as they apply only to the thread they
 | 
						|
came from.
 | 
						|
 | 
						|
You can create custom local objects by subclassing the local class:
 | 
						|
 | 
						|
  >>> class MyLocal(local):
 | 
						|
  ...     number = 2
 | 
						|
  ...     initialized = False
 | 
						|
  ...     def __init__(self, **kw):
 | 
						|
  ...         if self.initialized:
 | 
						|
  ...             raise SystemError('__init__ called too many times')
 | 
						|
  ...         self.initialized = True
 | 
						|
  ...         self.__dict__.update(kw)
 | 
						|
  ...     def squared(self):
 | 
						|
  ...         return self.number ** 2
 | 
						|
 | 
						|
This can be useful to support default values, methods and
 | 
						|
initialization.  Note that if you define an __init__ method, it will be
 | 
						|
called each time the local object is used in a separate thread.  This
 | 
						|
is necessary to initialize each thread's dictionary.
 | 
						|
 | 
						|
Now if we create a local object:
 | 
						|
 | 
						|
  >>> mydata = MyLocal(color='red')
 | 
						|
 | 
						|
Now we have a default number:
 | 
						|
 | 
						|
  >>> mydata.number
 | 
						|
  2
 | 
						|
 | 
						|
an initial color:
 | 
						|
 | 
						|
  >>> mydata.color
 | 
						|
  'red'
 | 
						|
  >>> del mydata.color
 | 
						|
 | 
						|
And a method that operates on the data:
 | 
						|
 | 
						|
  >>> mydata.squared()
 | 
						|
  4
 | 
						|
 | 
						|
As before, we can access the data in a separate thread:
 | 
						|
 | 
						|
  >>> log = []
 | 
						|
  >>> thread = threading.Thread(target=f)
 | 
						|
  >>> thread.start()
 | 
						|
  >>> thread.join()
 | 
						|
  >>> log
 | 
						|
  [[('color', 'red'), ('initialized', True)], 11]
 | 
						|
 | 
						|
without affecting this thread's data:
 | 
						|
 | 
						|
  >>> mydata.number
 | 
						|
  2
 | 
						|
  >>> mydata.color
 | 
						|
  Traceback (most recent call last):
 | 
						|
  ...
 | 
						|
  AttributeError: 'MyLocal' object has no attribute 'color'
 | 
						|
 | 
						|
Note that subclasses can define slots, but they are not thread
 | 
						|
local. They are shared across threads:
 | 
						|
 | 
						|
  >>> class MyLocal(local):
 | 
						|
  ...     __slots__ = 'number'
 | 
						|
 | 
						|
  >>> mydata = MyLocal()
 | 
						|
  >>> mydata.number = 42
 | 
						|
  >>> mydata.color = 'red'
 | 
						|
 | 
						|
So, the separate thread:
 | 
						|
 | 
						|
  >>> thread = threading.Thread(target=f)
 | 
						|
  >>> thread.start()
 | 
						|
  >>> thread.join()
 | 
						|
 | 
						|
affects what we see:
 | 
						|
 | 
						|
  >>> mydata.number
 | 
						|
  11
 | 
						|
 | 
						|
>>> del mydata
 | 
						|
"""
 | 
						|
 | 
						|
__all__ = ["local"]
 | 
						|
 | 
						|
# We need to use objects from the threading module, but the threading
 | 
						|
# module may also want to use our `local` class, if support for locals
 | 
						|
# isn't compiled in to the `thread` module.  This creates potential problems
 | 
						|
# with circular imports.  For that reason, we don't import `threading`
 | 
						|
# until the bottom of this file (a hack sufficient to worm around the
 | 
						|
# potential problems).  Note that almost all platforms do have support for
 | 
						|
# locals in the `thread` module, and there is no circular import problem
 | 
						|
# then, so problems introduced by fiddling the order of imports here won't
 | 
						|
# manifest on most boxes.
 | 
						|
 | 
						|
class _localbase(object):
 | 
						|
    __slots__ = '_local__key', '_local__args', '_local__lock'
 | 
						|
 | 
						|
    def __new__(cls, *args, **kw):
 | 
						|
        self = object.__new__(cls)
 | 
						|
        key = '_local__key', 'thread.local.' + str(id(self))
 | 
						|
        object.__setattr__(self, '_local__key', key)
 | 
						|
        object.__setattr__(self, '_local__args', (args, kw))
 | 
						|
        object.__setattr__(self, '_local__lock', RLock())
 | 
						|
 | 
						|
        if args or kw and (cls.__init__ is object.__init__):
 | 
						|
            raise TypeError("Initialization arguments are not supported")
 | 
						|
 | 
						|
        # We need to create the thread dict in anticipation of
 | 
						|
        # __init__ being called, to make sure we don't call it
 | 
						|
        # again ourselves.
 | 
						|
        dict = object.__getattribute__(self, '__dict__')
 | 
						|
        current_thread().__dict__[key] = dict
 | 
						|
 | 
						|
        return self
 | 
						|
 | 
						|
def _patch(self):
 | 
						|
    key = object.__getattribute__(self, '_local__key')
 | 
						|
    d = current_thread().__dict__.get(key)
 | 
						|
    if d is None:
 | 
						|
        d = {}
 | 
						|
        current_thread().__dict__[key] = d
 | 
						|
        object.__setattr__(self, '__dict__', d)
 | 
						|
 | 
						|
        # we have a new instance dict, so call out __init__ if we have
 | 
						|
        # one
 | 
						|
        cls = type(self)
 | 
						|
        if cls.__init__ is not object.__init__:
 | 
						|
            args, kw = object.__getattribute__(self, '_local__args')
 | 
						|
            cls.__init__(self, *args, **kw)
 | 
						|
    else:
 | 
						|
        object.__setattr__(self, '__dict__', d)
 | 
						|
 | 
						|
class local(_localbase):
 | 
						|
 | 
						|
    def __getattribute__(self, name):
 | 
						|
        lock = object.__getattribute__(self, '_local__lock')
 | 
						|
        lock.acquire()
 | 
						|
        try:
 | 
						|
            _patch(self)
 | 
						|
            return object.__getattribute__(self, name)
 | 
						|
        finally:
 | 
						|
            lock.release()
 | 
						|
 | 
						|
    def __setattr__(self, name, value):
 | 
						|
        lock = object.__getattribute__(self, '_local__lock')
 | 
						|
        lock.acquire()
 | 
						|
        try:
 | 
						|
            _patch(self)
 | 
						|
            return object.__setattr__(self, name, value)
 | 
						|
        finally:
 | 
						|
            lock.release()
 | 
						|
 | 
						|
    def __delattr__(self, name):
 | 
						|
        lock = object.__getattribute__(self, '_local__lock')
 | 
						|
        lock.acquire()
 | 
						|
        try:
 | 
						|
            _patch(self)
 | 
						|
            return object.__delattr__(self, name)
 | 
						|
        finally:
 | 
						|
            lock.release()
 | 
						|
 | 
						|
    def __del__(self):
 | 
						|
        import threading
 | 
						|
 | 
						|
        key = object.__getattribute__(self, '_local__key')
 | 
						|
 | 
						|
        try:
 | 
						|
            # We use the non-locking API since we might already hold the lock
 | 
						|
            # (__del__ can be called at any point by the cyclic GC).
 | 
						|
            threads = threading._enumerate()
 | 
						|
        except:
 | 
						|
            # If enumerating the current threads fails, as it seems to do
 | 
						|
            # during shutdown, we'll skip cleanup under the assumption
 | 
						|
            # that there is nothing to clean up.
 | 
						|
            return
 | 
						|
 | 
						|
        for thread in threads:
 | 
						|
            try:
 | 
						|
                __dict__ = thread.__dict__
 | 
						|
            except AttributeError:
 | 
						|
                # Thread is dying, rest in peace.
 | 
						|
                continue
 | 
						|
 | 
						|
            if key in __dict__:
 | 
						|
                try:
 | 
						|
                    del __dict__[key]
 | 
						|
                except KeyError:
 | 
						|
                    pass # didn't have anything in this thread
 | 
						|
 | 
						|
from threading import current_thread, RLock
 |