线上环境多线程是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,time
class A(object):
b = 1
my_obj = A()
def worker():
my_obj.b += 2
new_t = threading.Thread(target=worker)
new_t.start()
print(my_obj.b)
结果:
3
[Finished in 0.3s]
这里的线程把my_obj的值修改了。
使用Local的线程隔离:
可以通过实例化Local,把其对象当作一个字典进行使用。
import threading,time
from werkzeug.local import Local
class A(object):
b = 1
my_obj = Local()
my_obj.b = 1
def worker():
my_obj.b = 2
print("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: 2
In 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,time
from werkzeug.local import LocalStack
# 实例化一个LocalStack
s = LocalStack()
# 向栈内push元素
s.push(1)
# 读取栈顶元素,并不弹出
print(s.top)
# 弹出栈顶元素并读取
print(s.pop())
print(s.top)
结果
1
1
None
LocakStack()实现隔离
import threading,time
from werkzeug.local import LocalStack
s = 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做全局计数器。