ThreadLocal variable in Python

Source: Internet
Author: User
As a WSGI Tool Library, Werkzeug does not directly use the ThreadLocal class built in python due to some considerations. Instead, it implements a series of Local classes. Including simple Local, and LocalStack, LocalManager, and LocalProxy implemented on this basis. Next, let's take a look at the usage of these classes, the original design intention, and specific implementation skills. As a WSGI Tool Library, Werkzeug does not directly use the ThreadLocal class built in python due to some considerations. Instead, it implements a series of Local classes. Including simple Local, and LocalStack, LocalManager, and LocalProxy implemented on this basis. Next, let's take a look at the usage of these classes, the original design intention, and specific implementation skills.

Local class design

The Werkzeug designer believes that the ThreadLocal of python cannot meet the requirements, mainly for the following two reasons:

Werkzeug mainly uses "ThreadLocal" to meet the concurrency requirements. the ThreadLocal provided by python can only implement thread-based concurrency. In python, there are many other concurrency methods, such as the common greenlet, so you need to implement a Local object that supports the coroutine.

WSGI does not guarantee that a new thread is generated each time to process requests. that is to say, threads can be reused (a thread pool can be maintained to process requests ). In this way, if werkzeug uses the ThreadLocal provided by python, a "not clean (containing data related to previously processed requests)" thread will be used to process new requests.

To solve these two problems, werkzeug implements the Local class. The Local object can isolate data between threads and threads. In addition, it also supports clearing data in a thread or coroutine (in this way, corresponding data can be cleared after a request is processed, and then wait for the next request to arrive ).

How can we implement it? the idea is actually very simple. we have mentioned the ThreadLocal variable (I) in Python in depth, that is, creating a global Dictionary, then, the thread (or coroutine) identifier is used as the key, and the local data of the corresponding thread (or coroutine) is used as the value. Here, werkzeug is implemented according to the above ideas. However, it uses some dark magic of python and finally provides users with a clear and simple interface.

Implementation

The implementation of the Local class is analyzed in werkzeug. local with code of 8a84b62. Through the first two articles on ThreadLocal, we have learned the features and usage of the Local object. So here we will not provide examples of using the Local object. let's look at the code directly.

class Local(object):     __slots__ = ('__storage__', '__ident_func__')          def __init__(self):         object.__setattr__(self, '__storage__', {})         object.__setattr__(self, '__ident_func__', get_ident)     ...

Because there may be a large number of Local objects, in order to save space occupied by the Local object, use _ slots _ to write the attributes that can be owned by the Local object:

_ Storage __: The value is a dictionary used to save the actual data. the initialization is empty;

_ Ident_func __: The value is a function used to find the identifier of the current thread or coroutine.

Because the actual data of the Local object is saved in _ storage _, the operation on the Local attribute is actually an operation on _ storage. Here, we use the magic method _ getattr _ to intercept _ storage _ and _ ident_func _ to retrieve attributes, direct it to the data of the current thread or coroutine stored by _ storage. For the set or del attribute values, use _ setattr _ and _ setattr _ respectively (for details about these magic methods, see attribute control ). The key code is as follows:

def __getattr__(self, name):     try:         return self.__storage__[self.__ident_func__()][name]     except KeyError:         raise AttributeError(name)  def __setattr__(self, name, value):     ident = self.__ident_func__()     storage = self.__storage__     try:         storage[ident][name] = value     except KeyError:         storage[ident] = {name: value}  def __delattr__(self, name):     try:         del self.__storage__[self.__ident_func__()][name]     except KeyError:         raise AttributeError(name)

Suppose we have N threads or coroutines with ID 1, 2,..., N. each thread stores some Local data with a Local object. the content of the Local object is shown in:

In addition, the Local class provides the _ release_local _ method to release the data stored by the current thread or coroutine.

Local extension interface

Werkzeug implements LocalStack and LocalManager based on Local to provide more friendly interface support.

LocalStack

LocalStack encapsulates Local to implement an independent stack structure of a thread (or coroutine). there are specific usage methods in the annotations. A simple example is as follows:

ls = LocalStack() ls.push(12) print ls.top    # 12 print ls._local.__storage__ # {140735190843392: {'stack': [12]}}

The implementation of LocalStack is interesting. it uses a Local object as its own attribute _ local, and then defines the interface push, pop and top methods to perform corresponding stack operations. Here, the list of _ local. _ storage _. _ local. _ ident_func _ () is used to simulate the stack structure. In the push, pop, and top interfaces, you can simulate stack operations by operating on this list. Note that when obtaining this list within the interface function, you do not need to be as complex as the list above, you can directly use the getattr () method of _ local. Taking the push function as an example, the implementation is as follows:

def push(self, obj):     """Pushes a new item to the stack"""     rv = getattr(self._local, 'stack', None)     if rv is None:         self._local.stack = rv = []     rv.append(obj)     return rv

The implementation of pop and top is similar to that of general stacks. they all perform operations on the list of stack = getattr (self. _ local, 'stack', None. In addition, LocalStack also allows us to customize _ ident_func __. here, the built-in function property is used to generate the descriptor and encapsulate the get and set operations of _ ident_func, A property value _ ident_func _ is provided as an interface. the specific code is as follows:

def _get__ident_func__(self):     return self._local.__ident_func__  def _set__ident_func__(self, value):     object.__setattr__(self._local, '__ident_func__', value) __ident_func__ = property(_get__ident_func__, _set__ident_func__) del _get__ident_func__, _set__ident_func__

LocalManager

Both Local and LocalStack are single objects independent of threads or coroutines. in many cases, we need a separate thread or coroutine container, to organize multiple Local or LocalStack objects (just as we use a list to organize multiple int or string types ).

Werkzeug implements LocalManager. it stores the managed Local or LocalStack object through a list type attribute locals, and provides the cleanup method to release all Local objects. In Werkzeug, the main interface of LocalManager is the method make_middleware of the decorator. the code is as follows:

def make_middleware(self, app):     """Wrap a WSGI application so that cleaning up happens after     request end.     """     def application(environ, start_response):         return ClosingIterator(app(environ, start_response), self.cleanup)     return application

This modifier registers the callback function cleanup. after a thread (or coroutine) processes the request, cleanup is called to clean up the Local or LocalStack objects managed by it (the implementation of ClosingIterator is in werkzeug. in wsgi ). The following is a simple example of using LocalManager:

From werkzeug. local import Local, LocalManager local = Local () local_2 = Local () local_manager = LocalManager ([local, local2]) def application (environ, start_response): local. request = request (environ )... # After the application is processed, the content of local_manager is automatically cleared.

Through make_middleware of LocalManager, we can clear all the Local or LocalStack objects after a thread (coroutine) processes a request, so that this thread can process another request. So far, the second problem mentioned at the beginning of the article can be solved. Werkzeug. local also implements a LocalProxy to act as the proxy of the Local object, which is also worth learning.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.