threading.local 对象

一. threading.local 对象

1.1 问题引入

多个线程操作同一个变量,如果不加锁,会出现数据错乱问题

1.2 作用

线程变量: 意思是threading.local中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。threading.local为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量, 从而防止数据错乱

二. 代码展示

2.1 不使用local对象 >>> 需加锁

------示例1------

from threading import Thread
import time
from threading import Lock

lqz = -1

def task(lock1):
    lock1.acquire()
    global lqz
    tem = lqz
    time.sleep(0.0001)
    lqz = tem + 1
    print('---', lqz)
    lock1.release()

lock = Lock()

print(lqz)

for i in range(10):
    t = Thread(target=task, args=(lock,))
    t.start()
    t.join()

print(lqz)

------示例2------

from threading import Thread
import time
from threading import Lock

lqz = -1

def task(lock1):
    lock1.acquire()
    global lqz
    tem = lqz
    time.sleep(0.0001)
    lqz = tem + 1
    print('---', lqz)
    lock1.release()

lock = Lock()

print(lqz)

for i in range(10):
    t = Thread(target=task, args=(lock,))
    t.start()
    t.join()

print(lqz)

2.1 使用local对象

from threading import Thread,get_ident
from threading import local
import time


# 特殊的对象
lqz = local()

def task(arg):
    lqz.value = arg
    time.sleep(0.1)
    print('第:%s条线程的值为:%s'%(get_ident(),lqz.value))


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()

三. 自定义local支持线程和协程

3.1 问题引入

1. flask 的request,和session 都是全局的,但是我们在不同的视图函数中使用的 是正对于当前这次请求的对象,它的底层就是基于local写的

2. flask部署支持多进程线程架构,也支持协程架构,原因时 flask 内部重写了 local,让它支持线程和协程

3.2 local实现的本质

1. 定义一个字典变量, 以线程或协程 id 做为 key, 相关数据做为 value 进行存储。

1. 定义变量
    local: {'线程id号':{}}

2. 设置值
    一个线程时:local.val='lqz'   >>>     {'线程1 id号':{val:lqz},}
    多个线程时:local.val='pyy'   >>>     {'线程1 id号':{val:lqz}, '线程2 id号':{val:pyy},}

3. 取值
    print(local.val)  --->>>  l = {'线程1id号':{val:lqz}, '线程2id号':{val:pyy},}  --->>>  先当前线程的id号:get_ident()  --->>>   l[get_ident(线程1)]['val']

3.3 实现自定义 threading.local

1. 方式一:>>> 通过字典自定义threading.local
from threading import get_ident, Thread
import time

storage = {}


def set(k, v):
    ident = get_ident()          # 当前线程id号

    if ident in storage:         # 如果当前线程id号在字典中,表示修改值,直接改即可
        storage[ident][k] = v
    else:                        # 新增
        storage[ident] = {k: v}

def get(k):
    ident = get_ident()
    return storage[ident][k]

def task(arg):
    set('val', arg)
    v = get('val')
    time.sleep(0.01)
    print(v)

for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()
2. 方式二:>>> 面向对象
from threading import get_ident, Thread
import time


class Local(object):
    storage = {}

    def set(self, k, v):
        ident = get_ident()
        if ident in Local.storage:
            Local.storage[ident][k] = v
        else:
            Local.storage[ident] = {k: v}

    def get(self, k):
        ident = get_ident()
        return Local.storage[ident][k]


obj = Local()


def task(arg):
    obj.set('val', arg)
    v = obj.get('val')
    time.sleep(0.01)
    print(v)


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()

3. 方式三:>>> 重写类的 __ setatt r__ __ getattr __
from threading import get_ident, Thread
import time


class Local(object):
    storage = {}

    def __setattr__(self, k, v):
        ident = get_ident()
        if ident in Local.storage:
            Local.storage[ident][k] = v
        else:
            Local.storage[ident] = {k: v}

    def __getattr__(self, k):
        ident = get_ident()
        return Local.storage[ident][k]


obj = Local()  # 多个local对象公用一个storage


def task(arg):
    obj.val = arg
    v = obj.val
    time.sleep(0.01)
    print(v)


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()
4. 方式四:>>> 兼容线程和协程(完整版)
# 如果代码是以线程写的, 则使用的是获取线程号的方法。反之,则是获取协程号的方法
try:
    from greenlet import getcurrent as get_ident
except Exception as e:
    from threading import get_ident


from threading import Thread
import time


class Local(object):
    def __init__(self):
        # self.storage = {}   # 只要self.属性,就会调用 __setattr__,内部又掉了self.storage--->递归了
        # 类来调用对象的绑定方法__setattr__,这个方法就会变成函数,有几个值就要传几个值
        # 本质就是完成 self.storage = {} 要完成的事,但是不会触发递归调用
        # setattr(self,'storage', {})  # 反射的方式设置值,也会触发递归

        object.__setattr__(self, 'storage', {})

    def __setattr__(self, k, v):
        ident = get_ident()
        if ident in self.storage:
            self.storage[ident][k] = v
        else:
            self.storage[ident] = {k: v}

    def __getattr__(self, k):
        ident = get_ident()                  # 在协程中,gevent中是获取协程id号,如果在线程中,获取的是线程id号
        return self.storage[ident][k]


obj = Local()                                # 每个local对象,用自己的字典


def task(arg):
    obj.val = arg
    v = obj.val
    time.sleep(0.01)
    print(v)


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()
posted @ 2023-05-24 17:06  codegjj  阅读(8)  评论(0编辑  收藏  举报