多任务-任务与任务之间单独数据保存
在python中我们想要针对多任务如 多线程,每一个任务调用期间保存一份属于任务特有的变量时可以使用thread.local 线程本地变量或上下文变量, thread.local是针对于多线程的,在多线程中共用一个全局变量,但是每个线程变量从这个全局变量中获取、设置的变量针对于本线程,通过thread.local 可以实现线程隔离
thread.local
import threading, time
local = threading.local()
def run(name):
# 获取全局变量中保存的当前线程的属性变量
result = "调用run方法任务是: {}, 本次线程任务本地local变量为:{}".format(name, local.name)
print(result)
local.name = local.name + "000000"
def task(name):
# 全局线程变量设置name属性为当前调用的线程名称
local.name = name
print(f"{name} 线程local变量为: {local.name}")
# 休眠一秒让其他线程也执行上面的操作
time.sleep(1)
# 获取全局变量中local对象的name属性
result = "线程任务{} local变量为: {}\n".format(name, local.name)
print(result)
run(name)
result = "线程任务{} local变量为: {}\n".format(name, local.name)
print(result)
if __name__ == '__main__':
for i in range(3):
t = threading.Thread(target=task, args=(f"任务{i}",))
t.start()
time.sleep(5)
print("主线程local变量是: ", getattr(local, "name", None))
输出
任务0 线程local变量为: 任务0
任务1 线程local变量为: 任务1
任务2 线程local变量为: 任务2
线程任务任务2 local变量为: 任务2
线程任务任务0 local变量为: 任务0
调用run方法任务是: 任务0, 本次线程任务本地local变量为:任务0
线程任务任务1 local变量为: 任务1
调用run方法任务是: 任务1, 本次线程任务本地local变量为:任务1
调用run方法任务是: 任务2, 本次线程任务本地local变量为:任务2
主线程local变量是: None
可以看到,在程序执行期间,线程任务与任务之间的全局变量local中设置的属性是隔离的,每个线程都互不干扰
但是如果是在协程中的多任务时,这个thread.local就无法实现保存每个协程单独的变量了,这时候就需要引入ContextVar 上下文对象了
ContextVar
ContextVar是上下文对象,保存的是本次调用开始设置的上下文属性, 它针对的是程序的上下文调用
import time
from threading import Thread
from contextvars import ContextVar
ctx = ContextVar("ctx")
def run(name):
# 获取当前线程的上下文对象中存储的属性数据
result = "调用run方法任务是: {}, 本次函数调用上下文对象存储数据为:{}".format(name, ctx.get(None))
print(result)
def task(name):
# 获取当前函数调用是否已经存在上下文变量
ctx_result = ctx.get(None)
print(f"{name} ctx: {ctx_result}")
# 设置本次调用上下文变量属性
ctx.set(f"{name}")
time.sleep(1)
# 获取全局变量中上下文对象中保存当前调用的上下文属性
ctx_result = ctx.get(None)
result = "{}上下文结果为: {}\n".format(name, ctx_result)
print(result)
run(name)
if __name__ == '__main__':
for i in range(3):
t = Thread(target=task, args=(f"任务{i}",))
t.start()
time.sleep(5)
print("主线程上下文对象数据是: ", ctx.get(None))
输出
任务0 ctx: None
任务1 ctx: None
任务2 ctx: None
任务0上下文结果为: 任务0
任务2上下文结果为: 任务2
调用run方法任务是: 任务2, 本次函数调用上下文对象存储数据为:任务2
任务1上下文结果为: 任务1
调用run方法任务是: 任务0, 本次函数调用上下文对象存储数据为:任务0
调用run方法任务是: 任务1, 本次函数调用上下文对象存储数据为:任务1
主线程上下文对象数据是: None
可以看到,ContextVar类似于thread.loacl效果,更重要的是它使用与异步任务中
import asyncio
from contextvars import ContextVar
import time
ctx = ContextVar("ctx")
def run(name):
# 获取当前任务调用的上下文对象中存储的属性数据
result = "调用run方法任务是: {}, 本次函数调用上下文对象存储数据为:{}".format(name, ctx.get(None))
print(result)
async def task(name):
# 获取当前任务调用是否已经存在上下文变量
ctx_result = ctx.get(None)
print(f"{name} ctx: {ctx_result}")
# 设置本次调用上下文变量属性
ctx.set(f"{name}")
await asyncio.sleep(1)
# 获取全局变量中上下文对象中保存当前调用的上下文属性
ctx_result = ctx.get(None)
result = "{}上下文结果为: {}\n".format(name, ctx_result)
print(result)
run(name)
async def main():
task_list = [task(f"任务{i}") for i in range(3)]
await asyncio.gather(*task_list)
print("主协程中的上下文对象变量为: ", ctx.get(None))
if __name__ == '__main__':
asyncio.run(main())
输出
任务0 ctx: None
任务1 ctx: None
任务2 ctx: None
任务0上下文结果为: 任务0
调用run方法任务是: 任务0, 本次函数调用上下文对象存储数据为:任务0
任务1上下文结果为: 任务1
调用run方法任务是: 任务1, 本次函数调用上下文对象存储数据为:任务1
任务2上下文结果为: 任务2
调用run方法任务是: 任务2, 本次函数调用上下文对象存储数据为:任务2
主协程中的上下文对象变量为: None
总结:
thread.local 是针对于线程与线程之间的任务独立数据存储,贯穿整个线程,如果在task 函数中没有设置local.name ,在 run方法中设置了,那么,在task中调用完run方法后task也可以获取到run方法中设置的线程变量,
而ContextVar是针对函数任务调用上下文的数据存储,在设置之后后面的调用才能使用整个上下文属性, 如果没有在task方法中设置上下文属性而是在run方法中设置的话,那么task中是无法使用这个上下文属性的