在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()