美文网首页
Flask的ThreadLocal和LocalProxy

Flask的ThreadLocal和LocalProxy

作者: 普通的人形使 | 来源:发表于2018-02-28 00:08 被阅读0次
    class Local(object):
        __slots__ = ('__storage__', '__ident_func__')
    
        def __init__(self):
            object.__setattr__(self, '__storage__', {})
            object.__setattr__(self, '__ident_func__', get_ident)
    
        def __iter__(self):
            return iter(self.__storage__.items())
    
        def __call__(self, proxy):
            """Create a proxy for a name."""
            return LocalProxy(self, proxy)
    
        def __release_local__(self):
            self.__storage__.pop(self.__ident_func__(), None)
    
        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)
    

    这是flask里使用的ThreadLocal的定义,实际上是Werkzeug实现的。可以看出,Local对象在初始化时被绑定了两个属性:__storage____ident_func__,其中__storage__用于存储各种属性值,而__ident_func__被设置为函数get_ident,该函数可以获取当前线程或协程的id,这个id是对每个线程或者协程来说是唯一的,因此用这个id作为隔板将__storage__隔开(参考__getattr____setattr__两个方法的实现),可以保证ThreadLocal对象在被多个线程存取的情况下每个隔间的内容都不会被其他线程或协程修改,从而实现了线程安全。这样做的效果就是我们看到一个全局变量被多个线程存取,但是每个线程都察觉不到其他线程的存在。

    ThreadLocal的基础上,Werkzeug实现了两个类:LocalStackLocalProxy,前者其实就是线程安全的栈,即对于每个线程来说,LocalStack总是只能被自己push,pop。

    至于后者,每个LocalProxy对象在初始化时都接受一个callable的对象,这个callable对象在被调用后返回一个ThreadLocal对象,此后对这个LocalProxy对象的几乎所有操作都会被转发到ThreadLocal对象。
    这是LocalProxy的代码片段:

    class LocalProxy(object):
    
    
        def __init__(self, local, name=None):
            object.__setattr__(self, '_LocalProxy__local', local)
            object.__setattr__(self, '__name__', name)
            if callable(local) and not hasattr(local, '__release_local__'):
                # "local" is a callable that is not an instance of Local or
                # LocalManager: mark it as a wrapped function.
                object.__setattr__(self, '__wrapped__', local)
    
        def _get_current_object(self):
    
            if not hasattr(self.__local, '__release_local__'):
                return self.__local()
            try:
                return getattr(self.__local, self.__name__)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.__name__)
    
        @property
        def __dict__(self):
            try:
                return self._get_current_object().__dict__
            except RuntimeError:
                raise AttributeError('__dict__')
    

    可以看出对LocalProxy对象的属性读取都被转发到_get_current_object的调用返回上,而这个_get_current_object的调用实际是self.__local的调用,在初始化方法中,self.__local的被指定为传进来的callable对象(注意Python的对成员变量命名的trick),因此外界对LocalProxy的每次操作都会引发callable对象的调用,虽然我们看到的LocalProxy对象好像是一个静态的全局对象,但对它的每次读取属性都是在实时地调用传进来的callable对象。
    举个例子:

    request = LocalProxy(partial(_lookup_req_object, 'request'))
    

    这就是我们熟悉的flask的全局对象request,其中函数_lookup_req_object的作用是取当前线程/协程中LocalStack对象栈顶对象的某个属性,这里的partial(_lookup_req_object, 'request')实际返回了一个callable对象(参考偏函数的用法),这个callable对象每次被调用都会返回LocalStack栈顶的名为request的属性,实际就是当前要处理的request对象。与LocalProxy配合使用,就使得这个全局request对象总是指向当前线程/协程栈顶的request对象,也就是当前要处理的request对象。
    同理,flask对于session,g,current_app等全局对象都是同样的实现方式,只不过他们存在的栈不同,指向栈帧中的对象不同。

    相关文章

      网友评论

          本文标题:Flask的ThreadLocal和LocalProxy

          本文链接:https://www.haomeiwen.com/subject/cvmvxftx.html