ThreadLocal

博主 创建于 2015-10-15

之前有介绍过Flask的一些内容, 但是感觉不是很深入。比如对于g,session之类的属性到底是什么,为什么在不同的代码段里面可以直接使用?不同线程向里面存入数据,为什么不会冲突?

ThreadLocal

所谓的ThreadLocal是针对线程安全而言的一个概念。假设一个进程里面有一个变量,子线程能够同时读取修改它,那么多线程运行的时候为了保证不出现问题,就须要使用锁等额外的机制保证变量不会被意外的修改。这会增加编程的复杂度。

使用空间换效率的一个解决方案是为每一个线程创建一个单独的同名变量。在程序员看来,变量名一直没有改变;在程序看来,不同线程里面的变量实际指向了不同的引用。对于Python而言,我们可以创建一个字典,字典的key是线程ID,对应的值为该线程的变量值:

age = {}
age[get_current_threadid()] = 24
print(age[get_current_thread()])

每次取值,设值都需要使用当前线程ID作为索引,这当然麻烦。将上面例子中的age进行封装,调用age时,隐式调用get_current_threadid(),这样就会很方便了。比如werkzeug的Local就做了这样的事情:

class Local(object):
    ....

    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}
ident实际就是线程的ID号。假设你在多个线程里面设置了age的值,那么Local实例实际应该是这个样子的:
Local().__storage__ = { thread_id1: { age:12 },thread_id2: {age:24} ...} 

werkzeug里面还有一个ThreadStack对象,是以栈而不是字典的形式存储ThreadLocal对象,你可以自行参看werkzeug的代码,假设多个线程向这个LocalStack实例里面放入数据,那么LocalStack将会是这个样子的:

LocalStack()._local.__storage__ = { thread_id1:{ "stack": [12,...] }, thread_id2:{ "stack": [24,...] }...}

Flask的g,session,request以及current_app

以上四个对象都是LocalStack的具体应用。注意对于每一个flask进程,一共会有两个LocalStack。一个用于app以及g,一个用于session,request。以request为例,调用request实际上是调用了getattr(LocalStack().top, "request"),更加明确地讲,request实际为Flask.request_class 的实例,然后对于每一个传入的请求,都有一个对应的实例被压入LocalStack里面,在处理完请求后,对应的request也就被弹出了:

class Flask(object):
    ....    

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        ctx.push()
        ....
        response = self.full_dispatch_request()
        ....
        ctx.auto_pop(error)

对于app,g使用的LocalStack与request,session使用的LocalStack不同,后者在每一次请求到达的时候都会被创建请求结束后都会被删除;前者一般只会在线程生命周期内被创建一次。

SQLAlchemy的scoped_session

创建SQLAlchemy连接的时候,有时需要考虑线程安全问题。对于多个线程,创建独立的连接会比较安全:

from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

session_factory = sessionmaker(bind=some_engine)
Session = scoped_session(session_factory)

some_session = Session()

some_session就是ThreadLocal的对象。

操作: 评论

除非注明,ifconfiger博客文章均为原创,禁止出于商业目的全文转载。个人转载时,请以链接形式标明本文地址。

本文地址:https://ifconfiger.com/page/Talking-about-the-Threadlocal

nick 2015-11-05 18:27 回复