3.Flask 中的线程

3.Flask 中的线程

3.1 知识补充

3.1.1 线程补充

from threading import Thread
import time

class Foo():
    def __init__(self):
        self.num = 0

val = Foo()

def task(i):
    val.num = i
    time.sleep(1)
    print(val.num)

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

所有的线程都会去修改同一个值,会发生混乱。

# 当每个线程执行 val.xx 在内部会为此线程开辟一个空间,来存储相关的值。
# 找到线程对应的内存地址去取存储的 xx
val = threading.local()
def task(i):
    val.num = i
    time.sleep(1)
    print(val.num)

for i in range(4):
    t = Thread(target=task,args=(i,))
    t.start()
>>> 各自打印各自的值

主要是通过线程ID进行的处理。

3.1.2 栈和面向对象

:先进后出

# 列表可以维护成栈
v = [11,22,33,44,55]
v.appnend(66) # 进栈,压栈
v.pop() # 出栈

面向对象的方法

class Local(object):
    def __init__(self):
        object.__setattr__(self,"storage",{}) # 调用object的方法,防止发生递归报错
    def __setattr__(self, key, value):
        self.storage[key] = value

    def __getattr__(self, item):
        return self.storage.get(item)
local = Local()
local.x1 = 123 # 为对象赋值的时候对象内没有相关的属性,则执行__setattr__方法。此处已经完成重写。将值写入字典内部
print(local.x1) # 访问元素的时候执行__getattr__方法。

3.1.3 线程的唯一标识

def task():
    ident = get_ident()
    print("线程唯一标识为:",ident)

for i in range(20):
    t = Thread(target=task)
    t.start()

3.1.4 自定义的threadinglocal

import threading
'''
storage = {
	"线程ID":['维护成栈',]
	"1001":[value,]
}
'''
class Local(object):
    def __init__(self):
        object.__setattr__(self,"storage",{})
    def __setattr__(self, key, value):
        ident = threading.get_ident()
        if ident in self.storage:
            self.storage[ident][key].append(value)
        else:
            self.storage[ident] = {key:[value,]}
        self.storage[key] = value

    def __getattr__(self, item):
        ident = threading.get_ident()
        if ident not in self.storage:
            return
        return self.storage[ident][item][-1] # 取出最后一个值而不是弹栈,因为该对象的值可能还会使用
local = Local()
def task(arg):
    local.x1 = arg
    print(local.x1)
for i in range(5):
    t = threading.Thread(target=task,args=(i,))
    t.start()
>>> 0
>>> 1
>>> 2
>>> 3
>>> 4

3.2 flask 中local的实现

# 导入相关的包作为查看源码的入口
from flask import globals

根据源码中对象的创建直接找到相关的类。

以前版本的Flask中的loacl对象与自定定义的差别不大,识将维护的栈编程了字典的形式,此处的flask创建的值,并未封装对象,而是创建了一个ContextVar的对象;

# 该模块是py3.7之后加入的模块。
from contextvars import ContextVar

s = ContextVar("s") # 实例化对象
values = s.get({}).copy()
print(id(values)) # 1549945357504
# print(values)
values['aaa'] = 1223
s.set(values)
print(s.get({}))# {'aaa': 1223}
v = s.get({}).copy()
print(v) # 对象中已经存在的值得对象,相当于执行字典的copy()方法。{'aaa': 1223}
print(id(v)) # 1549945357440 返回一个新的字典。
v['bb'] = '456'
s.set(v)
print(s.get({})) #{'aaa': 1223, 'bb': '456'}

反射知识补充:

# 反射复习
class Foo:
    def __setattr__(self, key, value):
        print("setattr")

    def __getattr__(self, item):
        print("getattr")
        return item

obj = Foo()
setattr(obj,"aaa","bb") 
getattr(obj,'statck',[]) # 第三个值是默认值
print(obj.aaa)
>>> setattr
>>> getattr
>>> getattr
>>> aaa

通过上面的代码段可以得出,当使用反射为对象增加相关的属性的时候,回去执行对应的方法。

class Foo:
    def __setattr__(self, key, value):
        print("setattr")
obj = Foo()
rv = getattr(obj,'statck',[]) # 地三个参数是默认值。
print(rv)
>>> []

class Foo:
    def __setattr__(self, key, value):
        print("setattr")

    def __getattr__(self, item):
        print("getattr")
        return item

obj = Foo()
rv = getattr(obj,'statck',[])
print(rv)
>>> getattr
>>> statck

没有相关的返回值的时候,使用第三个参数的默认值进行替代。

class Local:
    __slots__ = ("_storage",) # 对实例化属性的约束。

    def __init__(self) -> None:,
        # 也是使用的是object方法,创建相关的变量 _storage 为ContextVar
        object.__setattr__(self, "_storage", ContextVar("local_storage"))

    def __call__(self, proxy: str) -> "LocalProxy":
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self) -> None:
        self._storage.set({}) # 将参数类型设置为字典。

    def __getattr__(self, name: str) -> t.Any:
        # 以前版本的处理流程,会在此处获取当当前的线程id进行处理,此处可能是已经同过内部的流程处理在相关的对象_storage中
        # 调用时getattr(self._local, "stack", []).copy()
        values = self._storage.get({}) # 取出_storage中的值,并赋值给values
        try:
            return values[name] # 取出对应的项,并将该项进行返回,该标识对应的对象
        except KeyError:
            raise AttributeError(name) from None

    def __setattr__(self, name: str, value: t.Any) -> None:
        # 以前版本的处理流程,会在此处获取当当前的线程id进行处理,此处可能是已经同过内部的流程处理在相关的对象_storage中
        values = self._storage.get({}).copy() # 返回一个复制的好的新字典
        values[name] = value # 为字典复制
        self._storage.set(values) # 将字典添加到对象_storage中

    def __delattr__(self, name: str) -> None:
        values = self._storage.get({}).copy() 
        try:
            del values[name]
            self._storage.set(values)
        except KeyError:
            raise AttributeError(name) from None
  • contextvar类似于多线程的thread.local;

slots属性设置为一个元组,元组内写类实例中定义好的属性,不能在动态添加属性,这样做的目的是节省内存空间,因为字典占用的内存空间远大于元组。关于__slots__中的更多作用,参考https://blog.csdn.net/sxingming/article/details/52892640

class LocalStack:
    """This class works similar to a :class:`Local` but keeps a stack
    of objects instead.  This is best explained with an example::

        >>> ls = LocalStack()
        >>> ls.push(42)
        >>> ls.top
        42
        >>> ls.push(23)
        >>> ls.top
        23
        >>> ls.pop()
        23
        >>> ls.top
        42

    They can be force released by using a :class:`LocalManager` or with
    the :func:`release_local` function but the correct way is to pop the
    item from the stack after using.  When the stack is empty it will
    no longer be bound to the current context (and as such released).

    By calling the stack without arguments it returns a proxy that resolves to
    the topmost item on the stack.

    .. versionadded:: 0.6.1
    """

    def __init__(self) -> None:
        self._local = Local() # 为当前线程创建local 对象,每个对象有自己相关的唯一标识。

    def __release_local__(self) -> None:
        self._local.__release_local__()

    def __call__(self) -> "LocalProxy":
        def _lookup() -> t.Any:
            rv = self.top
            if rv is None:
                raise RuntimeError("object unbound")
            return rv

        return LocalProxy(_lookup)

    def push(self, obj: t.Any) -> t.List[t.Any]:
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack", []).copy() #通过反射取出local对象创建对应的名称和值,
        # 访问local 对象的__getattr__方法,不存在则返回 [ ]
        rv.append(obj) #向其中加入相关的值
        self._local.stack = rv # 此时 rv 指向的内存地址和栈中指向的内存地址时相同的。
        return rv # 返回空列表。

    def pop(self) -> t.Any:
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, "stack", None) # 取出先关的值,不存在的时候返回空。
        if stack is None: # 空栈
            return None
        elif len(stack) == 1: # 检查是否为最后一项,满足该条件则将该栈进行销毁
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop() # 进行弹栈的操作,将相关的内容弹出

    @property
    def top(self) -> t.Any:
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1] # 最后一个,栈顶的元素进行使用。
        except (AttributeError, IndexError):
            return None

补充,字典的复制:

# 字典的复制直接复制两个不同的对象属于深拷贝。
>>> di1 = {1:"2"}
>>> di2 = di1.copy()
>>> id(di1) 
2240103479232
>>> id(di2) 
2240103479296
>>>

备注:此部分流程较为复杂,建议寻找相关的视频进行学习。

总结:

  • 1.在flask 内部中有一个local 类,他和therading.local的功能一样,为每个线程开辟单独的空间进行存取,内部是维护的线程(协程)ID为键的一个字典,进行数据的隔离,在较为新颖的版本中使用了相关的 ContextVar 对象。
  • 2.在flask 内部还有一个LocalStack 值,它依赖于Loacl 对象,localstatck会将该对象维护成一个栈。
posted @ 2022-07-21 20:09  紫青宝剑  阅读(261)  评论(0编辑  收藏  举报