Flask补充--threading.local对象

在Flask请求上下文中,我们发现Flask中current_app, g这两个对象以及request,session这两个对象,在整个Flask生命周期中,都只是一个对象,那当请求过来的时候,是怎么区分是哪个用户的呢?

current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

这里面主要用到了一个线程里面的Local对象以及偏函数partial

Local

在使用threading.local()之前,先了解一下局部变量和全局变量。

局部变量

import threading
import time

def foo():
    x = 0
    for i in range(100):
        time.sleep(0.0001)
        x += 1

    print(threading.current_thread(), x)


for i in range(5):
    threading.Thread(target=foo).start()
    
"""
运行结果:
<Thread(Thread-5, started 21732)> 100
<Thread(Thread-1, started 13300)> 100
<Thread(Thread-4, started 1568)> 100
<Thread(Thread-2, started 19864)> 100
<Thread(Thread-3, started 23984)> 100
"""

上面例子使用多线程,每个子线程完成不同的计算任务,x是局部变量。

每个子线程都要压栈,每个栈是独立的空间。每次压栈,局部变量x的作用域地址是不同的(线程独享),计算结果互不干扰。

全局变量

import threading
import time

x = 0

def foo():
    global x
    x = 0
    for i in range(100):
        time.sleep(0.0001)
        x += 1

    print(threading.current_thread(), x)


for i in range(5):
    threading.Thread(target=foo).start()
    
"""
运行结果:
<Thread(Thread-1, started 19492)> 491
<Thread(Thread-3, started 22692)> 497
<Thread(Thread-5, started 24344)> 498
<Thread(Thread-2, started 24428)> 499
<Thread(Thread-4, started 19000)> 500
"""

上面例子中当主线程中x是全局变量时,就变成了公共资源(也就是同一个对象),每个子线程互相干扰,最终导致错误的计算结果。

Python提供了 threading.local 类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其它线程不可见(本质上就是不同的线程使用这个对象时为其创建一个独立的字典)。

使用threading.local()

import threading
import time

loc = threading.local()

def foo():
    loc.x = 0
    for i in range(100):
        time.sleep(0.0001)
        loc.x += 1

    print(threading.current_thread(), loc.x)


for i in range(5):
    threading.Thread(target=foo).start()
    
"""
运行结果:
<Thread(Thread-1, started 20008)> 100
<Thread(Thread-2, started 23644)> 100
<Thread(Thread-5, started 10396)> 100
<Thread(Thread-4, started 22280)> 100
<Thread(Thread-3, started 19980)> 100
"""

每个子线程使用全局对象loc,但每个线程定义的属性loc.x是该线程独有的。

举一个错误的例子:,主线程中使用threading.local定义本地变量x,x在主线程中是独有的,子线程中就访问不到主线程的x的属性。

import threading
 
X='abc'
ctx=threading.local()
ctx.x=123 #主线程中定义x本地属性
print(ctx,type(ctx),ctx.x)
 
def work():
    print(X)
    print(ctx)
    print(ctx.x) #子线程访问不到
    print('Good job')
 
threading.Thread(target=work).start()

"""
运行结果:
<_thread._local object at 0x000001B22BBAB780> <class '_thread._local'> 123
abc
<_thread._local object at 0x000001B22BBAB780>
Exception in thread Thread-1:
Traceback (most recent call last):
  File "E:/Python学习笔记/flask/123.py", line 13, in work
    print(ctx.x)  # 子线程访问不到
AttributeError: '_thread._local' object has no attribute 'x'
"""

ctx全局对象对主线程和子线程都是可以使用的,主线程定义了属性x,但子线程在尝试访问属性x时,就相当于访问自己线程内的属性x,而自己线程并没有定义,就会抛出AttributeError异常:'_thread._local' object has no attribute 'x'

自定义threading.local

函数版

from threading import get_ident, Thread
import time

# 定义一个全局字典
storage = {}

def set(k, v):
    ident = get_ident()
    # print(ident)
    if ident in storage:
        storage[ident][k] = v
    else:
        storage[ident] = {k: v}

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

def task(arg):
    set('val', arg)
    v = get('val')
    # print(v)

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

print(storage) 
"""
{
    20552: {'val': 0}, 
    18496: {'val': 1}, 
    24476: {'val': 2}, 
    18700: {'val': 3}, 
    23740: {'val': 4}, 
    22160: {'val': 5}, 
    23896: {'val': 6}, 
    19204: {'val': 7}, 
    19028: {'val': 8}, 
    17972: {'val': 9}
}
"""

面向对象版

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')
    print(v)
    
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()
    
print(Local.storage)
"""
{
	19296: {'val': 0}, 
	20436: {'val': 1}, 
	8240: {'val': 2}, 
	19668: {'val': 3}, 
	16932: {'val': 4}
}
"""

通过setattr和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()
def task(arg):
    obj.val = arg
    print(obj.val)
    
for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()

每个对象有自己的存储空间(字典)

from threading import get_ident, Thread
import time


class Local(object):

    def __init__(self):
        object.__setattr__(self, 'storage', {})
        # print(1, self.__dict__)
        # self.aaa = {}
        # print(self.__dict__)

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

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


obj = Local()
# print(2, obj.__dict__)


def task(arg):
    obj.val = arg
    obj.xxx = arg
    print(obj.val)
    # print(obj.__dict__)


for i in range(10):
    t = Thread(target=task, args=(i,))
    t.start()
原文地址:https://www.cnblogs.com/Hades123/p/11779686.html