(十八)Flask之threaing.local()对象
0、引子:
- 如下是一段很基础的多线程代码:
from threading import Threaddemo = 0def task(arg):global demodemo = argprint(demo)for i in range(10):t = Thread(target=task, args=(i, ))t. start()
当程序运行时,可能会看到输出的顺序是混乱的,因为多个线程是并发执行的。这意味着它们可以同时访问和修改demo
变量。因此,你可能会看到输出中的数字在不同的线程间交替出现。这就是多线程并发的典型行为。
简单分析,我上述给出的输出却是有序的?
短任务和快速启动:
task
函数的工作量很小,仅仅是一个赋值操作和一个打印操作。因此,当主线程在循环中迅速启动线程时,每个线程可能会在下一个线程启动之前完成其任务。
加个睡眠:
from threading import Thread
import timedemo = 0def task(arg):global demodemo = argtime.sleep(2)print(demo)for i in range(10):t = Thread(target=task, args=(i, ))t. start()
由于线程的启动和执行是并发的,并且demo
是一个全局变量,多个线程可能在休眠结束后几乎同时打印它。加上time.sleep(2)
的存在,这将使数据竞争更加明显,因此,打印的demo
的值可能会与线程接收到的arg
值不同。大部分情况下,输出可能会是连续的最后几个数字(如9、9、9…),但这并不是固定的,具体取决于线程调度和执行的实际顺序。
为了避免上述问题:就需要使用锁或其他同步机制来确保线程安全地访问共享资源。
而结合Flask,就来看其他的同步机制:如使用threading.local()对象!
一、threading.local()对象
threading.local()
在threading
模块中是一个非常有用的工具,它提供了一个创建线程局部数据的方法。这意味着每个线程都有其独立的存储变量的实例,而不同线程之间不会相互干扰。
当你创建一个threading.local()
对象,每个线程可以为该对象赋予自己的属性值,而这些属性值对其他线程是不可见的。
比如:
from threading import Thread
from threading import local
import timedemo = local()def task(arg):demo.value = argtime.sleep(2)print(demo.value)for i in range(10):t = Thread(target=task, args=(i,))t.start()
回到Flask,Flask为什么需要threading.local()的功能?
回想一下前面讲的session部分,如果一下有很多人的请求进来,Flask处理可以开多个线程,但是每个人的请求都要单独开辟一块空间,去存储对应的数据,每个人的空间相互隔离开来,保证不出现紊乱。这样Flask就可以处理并发了!
仔细回想一下session部分:
当你访问例如
flask.request
或flask.session
时,你实际上是在访问线程(或其他上下文)特定的变量。这意味着在同一个应用中,多个请求可以同时被处理,每个请求都有自己的request
和session
对象,彼此之间完全隔离。
但是Flask没直接用threading.local(),而是自己实现了它!
下面就来研究下如何自己实现这个threading.local()的功能?
- 提示:每个线程都有自己的唯一标识。
from threading import Thread
from threading import get_identdef task(arg):print(get_ident())for i in range(10):t = Thread(target=task, args=(i, ))t. start()
思路就是为每个线程维护一个字典:这个字典以线程id为key,值为value。形如下:
{6832: {va1: 0, va2: 2},8184: {va1: 6, va2: 5},16184: {va1: 0, va2: 11},
}