python thread.local 和 ContextVar 使用

多任务-任务与任务之间单独数据保存

在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中是无法使用这个上下文属性的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值