线上环境多线程是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对象,不清楚会有多少个请求会发送过来,如果枚举实例化是不现实的

  1. request1 = Request()
  2. request2 = Request()
  3. ...

这里可以利用到request指向一个字典来解决问题。

对于多线程,每个线程都有一个唯一的标识。

伪代码:

  1. request = {thread_key1:Request1,...}

线程隔离对象 Local

werkzeug库,local模块里的Local对象。实现线程隔离的方式就是字典。
Local将字典封装为了一个对象。
在其setattr方法中,将线程(ident)号作为字典的键。

普通的对象:

  1. import threading,time
  2. class A(object):
  3. b = 1
  4. my_obj = A()
  5. def worker():
  6. my_obj.b += 2
  7. new_t = threading.Thread(target=worker)
  8. new_t.start()
  9. print(my_obj.b)

结果:

  1. 3
  2. [Finished in 0.3s]

这里的线程把my_obj的值修改了。

使用Local的线程隔离:

可以通过实例化Local,把其对象当作一个字典进行使用。

  1. import threading,time
  2. from werkzeug.local import Local
  3. class A(object):
  4. b = 1
  5. my_obj = Local()
  6. my_obj.b = 1
  7. def worker():
  8. my_obj.b = 2
  9. print("In new thread,my_obj.b is:",str(my_obj.b))
  10. new_t = threading.Thread(target=worker)
  11. new_t.start()
  12. print("In main thread,my_obj.b is:",str(my_obj.b))

结果为:

  1. In new thread,my_obj.b is: 2
  2. In main thread,my_obj.b is: 1
  3. [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。

  1. import threading,time
  2. from werkzeug.local import LocalStack
  3. # 实例化一个LocalStack
  4. s = LocalStack()
  5. # 向栈内push元素
  6. s.push(1)
  7. # 读取栈顶元素,并不弹出
  8. print(s.top)
  9. # 弹出栈顶元素并读取
  10. print(s.pop())
  11. print(s.top)

结果

  1. 1
  2. 1
  3. None

LocakStack()实现隔离

  1. import threading,time
  2. from werkzeug.local import LocalStack
  3. s = LocalStack()
  4. s.push(1)
  5. def work():
  6. print("最开始,非主线程的s的栈顶元素是",s.top,'\n','下面将操作push2')
  7. print('*'*18)
  8. s.push(2)
  9. print("非主线程的s.push了一个2之后的栈顶元素是",s.top)
  10. new_t = threading.Thread(target=work)
  11. new_t.start()
  12. print("主线程的s的栈顶元素是",s.top,'\n')

结果是:

  1. 最开始,非主线程的s的栈顶元素是 None
  2. 主线程的s的栈顶元素是 1
  3. 下面将操作push2
  4. ******************
  5. 非主线程的spush了一个2之后的栈顶元素是 2
  6. [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做全局计数器。