diff options
author | Jim Fulton <jim@zope.com> | 2004-07-14 19:11:50 (GMT) |
---|---|---|
committer | Jim Fulton <jim@zope.com> | 2004-07-14 19:11:50 (GMT) |
commit | d15dc06df062fdf0fe8badec2982c6c5e0e28eb0 (patch) | |
tree | e0295c2c22fa7c2e702b37a1d7afd77547a8894f /Lib | |
parent | e827437f45510d9cdd1e7fa561da8084f69ca698 (diff) | |
download | cpython-d15dc06df062fdf0fe8badec2982c6c5e0e28eb0.zip cpython-d15dc06df062fdf0fe8badec2982c6c5e0e28eb0.tar.gz cpython-d15dc06df062fdf0fe8badec2982c6c5e0e28eb0.tar.bz2 |
Implemented thread-local data as proposed on python-dev:
http://mail.python.org/pipermail/python-dev/2004-June/045785.html
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/_threading_local.py | 237 | ||||
-rw-r--r-- | Lib/dummy_threading.py | 20 | ||||
-rw-r--r-- | Lib/test/test_threading_local.py | 26 | ||||
-rw-r--r-- | Lib/threading.py | 10 |
4 files changed, 292 insertions, 1 deletions
diff --git a/Lib/_threading_local.py b/Lib/_threading_local.py new file mode 100644 index 0000000..3509493 --- /dev/null +++ b/Lib/_threading_local.py @@ -0,0 +1,237 @@ +"""Thread-local objects + +(Note that this module provides a Python version of thread + threading.local class. Deoending 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 it's 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 = mydata.__dict__.items() + ... items.sort() + ... 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 effecting this threads 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 +""" + +# Threading import is at end + +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 sire we don't cal it + # again ourselves. + dict = object.__getattribute__(self, '__dict__') + currentThread().__dict__[key] = dict + + return self + +def _patch(self): + key = object.__getattribute__(self, '_local__key') + d = currentThread().__dict__.get(key) + if d is None: + d = {} + currentThread().__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__(): + threading_enumerate = enumerate + __getattribute__ = object.__getattribute__ + + def __del__(self): + key = __getattribute__(self, '_local__key') + + try: + threads = list(threading_enumerate()) + except: + # if enumerate 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 nything in this thread + + return __del__ + __del__ = __del__() + +from threading import currentThread, enumerate, RLock diff --git a/Lib/dummy_threading.py b/Lib/dummy_threading.py index 2e070aa..4d0b744 100644 --- a/Lib/dummy_threading.py +++ b/Lib/dummy_threading.py @@ -18,6 +18,7 @@ import dummy_thread # Declaring now so as to not have to nest ``try``s to get proper clean-up. holding_thread = False holding_threading = False +holding__threading_local = False try: # Could have checked if ``thread`` was not in sys.modules and gone @@ -37,20 +38,39 @@ try: held_threading = sys_modules['threading'] holding_threading = True del sys_modules['threading'] + + if '_threading_local' in sys_modules: + # If ``_threading_local`` is already imported, might as well prevent + # trying to import it more than needed by saving it if it is + # already imported before deleting it. + held__threading_local = sys_modules['_threading_local'] + holding__threading_local = True + del sys_modules['_threading_local'] + import threading # Need a copy of the code kept somewhere... sys_modules['_dummy_threading'] = sys_modules['threading'] del sys_modules['threading'] + sys_modules['_dummy__threading_local'] = sys_modules['_threading_local'] + del sys_modules['_threading_local'] from _dummy_threading import * from _dummy_threading import __all__ finally: # Put back ``threading`` if we overwrote earlier + if holding_threading: sys_modules['threading'] = held_threading del held_threading del holding_threading + # Put back ``_threading_local`` if we overwrote earlier + + if holding__threading_local: + sys_modules['_threading_local'] = held__threading_local + del held__threading_local + del holding__threading_local + # Put back ``thread`` if we overwrote, else del the entry we made if holding_thread: sys_modules['thread'] = held_thread diff --git a/Lib/test/test_threading_local.py b/Lib/test/test_threading_local.py new file mode 100644 index 0000000..1258455 --- /dev/null +++ b/Lib/test/test_threading_local.py @@ -0,0 +1,26 @@ +import unittest +from doctest import DocTestSuite +from test import test_support + +def test_main(): + suite = DocTestSuite('_threading_local') + + try: + from thread import _local + except ImportError: + pass + else: + import _threading_local + local_orig = _threading_local.local + def setUp(): + _threading_local.local = _local + def tearDown(): + _threading_local.local = local_orig + suite.addTest(DocTestSuite('_threading_local', + setUp=setUp, tearDown=tearDown) + ) + + test_support.run_suite(suite) + +if __name__ == '__main__': + test_main() diff --git a/Lib/threading.py b/Lib/threading.py index e70c61b..c485524 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -15,7 +15,7 @@ from collections import deque # Rename some stuff so "from threading import *" is safe __all__ = ['activeCount', 'Condition', 'currentThread', 'enumerate', 'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', - 'Timer', 'setprofile', 'settrace'] + 'Timer', 'setprofile', 'settrace', 'local'] _start_new_thread = thread.start_new_thread _allocate_lock = thread.allocate_lock @@ -661,6 +661,14 @@ def enumerate(): _MainThread() +# get thread-local implementation, either from the thread +# module, or from the python fallback + +try: + from thread import _local as local +except ImportError: + from _threading_local import local + # Self-test code |