[Python自学] Flask框架 (4) (上下文)

一、上下文管理理论基础

1.线程数据隔离

多线程访问一个数据:当内存中存在一个数据,多个线程都可以对其进行修改,如果要保证数据的一致性,则需要对其进行加锁。

多线程操作自己的数据:当需要每个线程都只能操作自己的数据,而该数据需要放置到一个全局的空间(例如全局变量)。则需要对其进行数据隔离,即线程只能访问自己存储的数据。

在threading模块中,我们可以使用threading.local来实现线程之间的数据隔离:

import threading
import time

# 定义一个threading.local对象
obj = threading.local


def run(index):
    # 写入xxx=index
    obj.xxx = index


# 10个线程
for i in range(10):
    t = threading.Thread(target=run, agrs=(i,))
    t.start()

虽然每个线程都对obj写入了一个名为"xxx"的变量,值为自己的index。但是threading.local对象为各个线程做了数据隔离。

他的原理是,为每一个线程都开辟一块内存空间,实际上就是利用一个字典,将线程的唯一表示作为键key,线程存入的值放到该键对应的value中。如下所示:

# threading.local使用一个字典来保存各个线程的数据
{
    1233: {'xxx': 0},  # 第0个线程的tid为1233,存入的值放在对应的字典中
    1234: {'xxx': 1},
    1235: {'xxx': 2},
    1236: {'xxx': 3},
    1237: {'xxx': 4},
    1238: {'xxx': 5},
    1239: {'xxx': 6},
    1240: {'xxx': 7},
    1241: {'xxx': 8},
    1242: {'xxx': 9},
}

2.用字典实现一个threading.local类

import threading


class Local(object):
    DIC = {}  # DIC字典用于存放各线程的数据,通过key来隔离

    # 从DIC中线程tid对应的字典中获取值
    def __getattr__(self, item):
        tid = threading.get_ident()
        if tid in self.DIC:
            return self.DIC[tid].get(item)
        else:
            return None

    # 设置一个值,类似obj.xxx = 1
    def __setattr__(self, key, value):
        # 获取该线程的tid
        tid = threading.get_ident()
        # 如果DIC中存在键为tid的数据
        if tid in self.DIC:
            self.DIC[tid][key] = value
        else:
            self.DIC[tid] = {key: value}


# 创建一个Local对象
obj = Local()


def run(index):
    # 使用Local对象保存各线程的数据
    obj.xxx = index


# 开启10个线程
for i in range(10):
    t = threading.Thread(target=run, args=(i,))
    t.start()

# 打印最后的值
print(
    obj.DIC)  # {2852: {'xxx': 0}, 13520: {'xxx': 1}, 10756: {'xxx': 2}, 7488: {'xxx': 3}, 8484: {'xxx': 4}, 13924: {'xxx': 5}, 10668: {'xxx': 6}, 10252: {'xxx': 7}, 11348: {'xxx': 8}, 10736: {'xxx': 9}}

3.将Local支持协程

我们实现的Local类,达到了和threading.local一样的效果,可以隔离线程的数据。但是我们如果使用的是协程,则需要对其进行扩展。

import threading

try:
    import greenlet

    # 使用协程时,将协程获取唯一标识的方法赋值给get_ident
    get_ident = greenlet.getcurrent
    print("使用协程")
except Exception as e:
    print("使用线程")
    # 没有使用协程时,将线程获取唯一标识的方法赋值给get_ident
    get_ident = threading.get_ident


class Local(object):
    DIC = {}  # DIC字典用于存放各线程的数据,通过key来隔离

    # 从DIC中线程tid对应的字典中获取值
    def __getattr__(self, item):
        tid = get_ident()
        if tid in self.DIC:
            return self.DIC[tid].get(item)
        else:
            return None

    # 设置一个值,类似obj.xxx = 1
    def __setattr__(self, key, value):
        # 获取该线程的tid
        tid = get_ident()
        # 如果DIC中存在键为tid的数据
        if tid in self.DIC:
            self.DIC[tid][key] = value
        else:
            self.DIC[tid] = {key: value}


# 创建一个Local对象
obj = Local()


def run(index):
    # 使用Local对象保存各线程的数据
    obj.xxx = index


# 开启10个线程
for i in range(10):
    t = threading.Thread(target=run, args=(i,))
    t.start()

# 打印最后的值
print(obj.DIC)

要扩展支持协程,其实很简单,就是让唯一标识从线程的唯一标识替换为协程的唯一标识。

4.线程、协程数据隔离和Flask的关系

虽然threading.local和Flask没有直接的关系,但是Flask中实现了一套利用此原理的数据隔离机制。类似我们第3.节中实现的支持线程和协程的版本。

在Flask中,请求相关的数据和session等数据都是通过上下文管理的。我们可以将上下文看成一个全局的数据存放点。而我们需要对其数据进行隔离,因为Flask有可能底层会使用多线程、协程等方式来运行。

多个线程或协程会同时接受来自用户的请求,而如果不进行数据隔离,则可能数据会相互覆盖,从而导致数据错误。

##

猜你喜欢

转载自www.cnblogs.com/leokale-zz/p/12402284.html