Flask上下文

  在flask里的request和session的设置方式还是比较独特的,导入的这个两个东西给人感觉是全局变量,不像django,request是以参数方式传递,当然flask这种方式在单线程下,肯定是不会存在问题的,但是多线程了?

  试想这么一个场景:Alex先来设置了session,过一阵子再来取值,但是这段时间seven改掉了这个值,而Alex来取的值变成了seven设置的,这并不是我们想要的,那基于多线程下,你猜flask是怎么解决这个问题了?

  • 加锁,设置完后我先锁上,等下次用完再解锁
  • 给线程开辟单独的空间,用于保存这个

  如果是第一种方式,在执行效率上会大大折扣,所以猜测是第二种方式

  在深入研读flask源码前,我需要了解一下本地线程,它保证了即使是多线程的情况下,自己的值也是相互隔离的

import threading

local_values = threading.local()


def func(num):
    local_values.name = num  #这个就是隔离的变量
    import time
    time.sleep(1)
    print(local_values.name, threading.current_thread().name)


for i in range(20):
    th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
    th.start()

   所以上面的threading.local对象,用于为每个线程开辟一块空间来保存它独有的值

  所以就有了以下几种情况下,解决方案:

  • 情况一:单进程单线程,基于全局变量来做
  • 情况二:单进程多线程,threading.local对象
  • 情况三:单进程单线程(多个协程),那这个threading.local对象也做不到

  对于情况三,由于协程是在线程里并发的,而threading.local对象只是对线程起作用,似乎用它对多协程不起作用,那说这里,如果要支持,就需要自定义了

  在存储上,可以用字典来存储,但我们需要获取线程或协程的唯一标识

  • 线程  from _thread import get_ident
  • 协程 from greenlet import getcurrent as get_ident
"""
{
   1368:{}
}



"""
import threading
try:
    from greenlet import getcurrent as get_ident # 协程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident # 线程


class Local(object):
    def __init__(self):
        self.storage = {}
        self.get_ident = get_ident

    def set(self,k,v):
        ident = self.get_ident()
        origin = self.storage.get(ident)
        if not origin:
            origin = {k:v}
        else:
            origin[k] = v
        self.storage[ident] = origin

    def get(self,k):
        ident = self.get_ident()
        origin = self.storage.get(ident)
        if not origin:
            return None
        return origin.get(k,None)

local_values = Local()


def task(num):
    local_values.set('name',num)
    import time
    time.sleep(1)
    print(local_values.get('name'), threading.current_thread().name)


for i in range(20):
    th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
    th.start()

   上面方式就能完美的解决协程了

  不过在上面的设置和获取,我们可以优化一下,那就要用到面向对象的知识了

class Foo(object):

    def __init__(self):
        object.__setattr__(self, 'storage', {}) #这赋值方式就不会调用__setattr__
        # self.storage = {}  

    def __setattr__(self, key, value):
        # self.storage = {'k1':'v1'}  #在这里不能用这种方式,因为这个赋值过程就是在调用__setattr__,会陷入死循环
        print(key,value)

    def __getattr__(self, item):
        print(item)
        return 'df'


obj = Foo()

obj.x = 123  #调用__setattr__方法
# 对象.xx   #调用__getattr__方法

   优化后的就和flask实现的源码一致了

import threading
try:
    from greenlet import getcurrent as get_ident # 协程
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident # 线程


class Local(object):

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


    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)


local_values = Local()


def task(num):
    local_values.name = num
    import time
    time.sleep(1)
    print(local_values.name, threading.current_thread().name)


for i in range(20):
    th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
    th.start()

 

posted @ 2018-08-30 09:43  财经知识狂魔  阅读(178)  评论(0编辑  收藏  举报