线上环境多线程是webserver开启的,而不是flask本身,WebServer例如Nginx、Apache、Tomcat、IIS等。
线上环境一般也是不会通过app.run()的。
app.run
app.run本身,默认是处理单进程、单线程调试。可以通过在app.run中的参数加一个threaded=True实现多线程。也可以设置参数processes设置多进程。
真正的请求对象是Request而不是request
单线程是排队的,顺序执行,第一个请求结束才第二个才可以进行请求,request总会指向当前请求,不会出现混乱。
对于多线程,Request的实例化可能是同一时间,那么request指向的Request的对象是哪个是不确定的,因为变量名是同一个,都叫request,可能会导致请求的污染。
解决多线程引入的问题
对于多线程,Request的实例化可能是同一时间,那么request指向的Request的对象是哪个是不确定的,因为变量名是同一个,都叫request,可能会导致请求的污染。
线程隔离 字典
对于request对象,不清楚会有多少个请求会发送过来,如果枚举实例化是不现实的
request1 = Request()request2 = Request()...
这里可以利用到request指向一个字典来解决问题。
对于多线程,每个线程都有一个唯一的标识。
伪代码:
request = {thread_key1:Request1,...}
线程隔离对象 Local
werkzeug库,local模块里的Local对象。实现线程隔离的方式就是字典。
Local将字典封装为了一个对象。
在其setattr方法中,将线程(ident)号作为字典的键。
普通的对象:
import threading,timeclass A(object):b = 1my_obj = A()def worker():my_obj.b += 2new_t = threading.Thread(target=worker)new_t.start()print(my_obj.b)
结果:
3[Finished in 0.3s]
这里的线程把my_obj的值修改了。
使用Local的线程隔离:
可以通过实例化Local,把其对象当作一个字典进行使用。
import threading,timefrom werkzeug.local import Localclass A(object):b = 1my_obj = Local()my_obj.b = 1def worker():my_obj.b = 2print("In new thread,my_obj.b is:",str(my_obj.b))new_t = threading.Thread(target=worker)new_t.start()print("In main thread,my_obj.b is:",str(my_obj.b))
结果为:
In new thread,my_obj.b is: 2In main thread,my_obj.b is: 1[Finished in 0.6s]
可以看到主线程的值没有被修改
LocalStack
request_ctx_stack和app_ctx_stack都是LocalStack()的实例化对象。
LocalStack是可以进行线程隔离的栈。
LocakStack封装了Local对象。
使用LocalStack
LocakStack使用上和Local不太一样,Local是可以用 . 属性操作变量,而LocakStack是用方法操作,例如push()、pop()和top等。
这里的top是属性当作方法的使用,因为加了@property。
import threading,timefrom werkzeug.local import LocalStack# 实例化一个LocalStacks = LocalStack()# 向栈内push元素s.push(1)# 读取栈顶元素,并不弹出print(s.top)# 弹出栈顶元素并读取print(s.pop())print(s.top)
结果
11None
LocakStack()实现隔离
import threading,timefrom werkzeug.local import LocalStacks = LocalStack()s.push(1)def work():print("最开始,非主线程的s的栈顶元素是",s.top,'\n','下面将操作push2')print('*'*18)s.push(2)print("非主线程的s.push了一个2之后的栈顶元素是",s.top)new_t = threading.Thread(target=work)new_t.start()print("主线程的s的栈顶元素是",s.top,'\n')
结果是:
最开始,非主线程的s的栈顶元素是 None主线程的s的栈顶元素是 1下面将操作push2******************非主线程的spush了一个2之后的栈顶元素是 2[Finished in 0.9s]
这里有一点挺有意思的,非主线程的换行符的执行是在主线程打印之后。
使用线程隔离的意义
让request可以正确找到相应的线程对象。
使当前线程能够正确引用到他自己创建的对象,而不是引用到其他线程所创建的对象【对象可以保存状态】。
Local vs LocalStack vs Dict
Local使用字典的方式实现线程隔离,以线程号作为key;
LocalStack封装了Local,是线程隔离的栈结构;
如果一次封装解决不了问题,那么就多封装一次。
被线程隔离的对象
AppContext和RequestContext是被 线程隔离的对象。
current_app = LocalProxy(_find_app) #_find_app返回的是上下文的app核心对象。
current_app是全局唯一的,线程隔离没有意义,可以用于做全局计数器,但是这种方式不好,有线程安全问题,可以使用redis或者MySQL做全局计数器。
