Flask 上下文管理

为什么用threading.local?

我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程对同一块数据处理的冲突问题,一个办法就是加互斥锁,另一个办法就是利用threading.local

threading.local 实现的的基本思路: 给一个进程中的多个线程开辟独立的空间来分别保存它们的值.这样它就不会更改全局变量了.

情况一,开多个线程更改全局变量,然后每次线程打印全局变量的值

from threading import Thread
import time

local_values =3
def f(args):
    global local_values #更改全局变量
    local_values=args
    time.sleep(3) #如果这里不加等待时间,就不存在抢空间的说法
    print(local_values)#结果就等于最后一个更改的值

for i in range(20):
    thread_=Thread(target=f,args=(i,))
    thread_.start()

结果:

19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
19
View Code

这样就造成了数据不安全,本来我们设想的是1,2,3,4,5,6,7,8'...,最后怎么都变成19了,原因在于,线程之前数据是共享的,当你同时开多个线程时候,遇到io阻塞就会发生数据污染,也就是互斥现象.

怎么解决这个问题呢?

方法一:我们加互斥锁,让更改数据的那部分变成顺序执行,见互斥锁博客中的互斥锁关于线程

方法二:这个是我们要介绍的重点

就出现了local类

import threading
from threading import local
from threading import Thread
import time
#实例化一个local对象
local_values =local()
def f(args):
    local_values.name=args#给每次进程中添加内容
    time.sleep(1)
    print(local_values.name,threading.current_thread().name)

for i in range(20):
    thread_=Thread(target=f,args=(i,),name='线程%s'%(i))
    thread_.start()

结果:

0 线程0
1 线程1
6 线程6
4 线程4
5 线程5
2 线程2
3 线程3
11 线程11
10 线程10
9 线程9
7 线程7
8 线程8
16 线程16
17 线程17
14 线程14
12 线程12
15 线程15
13 线程13
19 线程19
18 线程18
View Code

这样就解决了,依据这个思路,我们自己实现给线程开辟独有的空间保存特有的值

threading.local对于flask处理并发

那么Flask也可以用thread.local来处理大量请求啊,但是Flask并不没有使用threading.local,而是使用了类似threading.local的东西来处理的并发请求

我们知道线程和协程都有自己的get_ident 或getcurrent也就是唯一标识,我们可以用这个唯一标识当做键,以每个线程中保存的值为value,为线程开辟独有的空间来保存值

import threading

try:
    from greenlet import getcurrent as get_ident  # 没有携程的时候就用线程
except Exception as e:
    from threading import get_ident

from threading import Thread
import time


# 创建一个类似于threading.Local的类
class Local_demo:
    def __init__(self):
        self.dict1 = {}  # 创建一个空字典用来保存所有的线程要存储的值

    def set(self, k, v):
        """
        把每个线程要储存的值放到字典中
        :param k:
        :param v:
        :return:
        """
        ident = get_ident()  # 获取每一个线程的唯一标识
        if ident in self.dict1:
            self.dict1[ident][k] = v
        else:
            self.dict1[ident] = {k: v}

    def get(self, k):
        """
        根据k值获取当前线程保留在字典中的值
        :param k:
        :return:
        """
        ident = get_ident()
        return self.dict1[ident][k]


obj = Local_demo()


def f(arg):
    obj.set("k", arg)
    time.sleep(3)
    print("%s存储的值是%s"%( threading.current_thread().name,obj.get('k')))

if __name__ == '__main__':
    #开启多线程
    for i in range(20):
        thread_ = Thread(target=f, args=(i,), name='线程%s' % (i))
        thread_.start()
View Code

结果:

线程3存储的值是3
线程1存储的值是1
线程0存储的值是0
线程2存储的值是2
线程8存储的值是8
线程6存储的值是6
线程5存储的值是5
线程4存储的值是4
线程7存储的值是7
线程15存储的值是15
线程14存储的值是14
线程13存储的值是13
线程12存储的值是12
线程11存储的值是11
线程10存储的值是10
线程9存储的值是9
线程19存储的值是19
线程18存储的值是18
线程17存储的值是17
线程16存储的值是16
View Code

让我们来继续优化下,注意我们在f函数中用到了obj.set来调用set()函数,我们可以用对象.没有的属性来调用__setattr__和__getattr__,这样吧set和get函数名换成__setattr__ 和 __getattr__ 这样就显得有逼格多了

第三种代码:

import threading

try:
    from greenlet import getcurrent as get_ident #没有携程的时候就用线程
except Exception as e:
    from threading import get_ident

from threading import Thread
import time



# 实例化一个local对象
class Local_demo(object):
    def __init__(self):
        # self.dict1 = {}  # 在这就不能这样创建字典了,因为有__setattr__,这样创建就会频繁调用该函数,变成递归无限了
        object.__setattr__(self,'dict1',{})#这里一定要用object来调用

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

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


obj = Local_demo()


def f(arg):
    obj.val=arg
    time.sleep(3)
    print(obj.val, threading.current_thread().name)


for i in range(20):
    thread_ = Thread(target=f, args=(i,), name='线程%s' % (i))
    thread_.start()
优化代码

结果:

2 线程2
1 线程1
0 线程0
4 线程4
3 线程3
7 线程7
6 线程6
5 线程5
10 线程10
9 线程9
8 线程8
12 线程12
13 线程13
11 线程11
16 线程16
15 线程15
14 线程14
18 线程18
17 线程17
19 线程19
View Code

我们再说回Flask,我们知道django中的request是直接当参数传给视图的,这就不存在几个视图修改同一个request的问题,但是flask不一样,flask中的request是导入进去的,就相当于全局变量,每一个视图都可以对它进行访问修改取值操作,这就带来了共享数据的冲突问题,解决的思路就是我们上边的第三种代码,利用协程和线程的唯一标识作为key,也是存到一个字典里,类中也是采用__setattr__和__getattr__方法来赋值和取值.

Flask中有个local对象建立方式和第三种代码的建立方式一样,利用协程和线程的唯一标识作为key,具体的形式为

{
                线程或协程唯一标识: { 'stack':[request],'xxx':[session,] },
                线程或协程唯一标识: { 'stack':[] },
                线程或协程唯一标识: { 'stack':[] },
                线程或协程唯一标识: { 'stack':[] },
            }

 

上下文管理机制

在Flask中提供了两种上下文管理机制

一个是请求上下文,另一个应用上下文

从源码中来说: 上下文管理流程分为三个阶段

我觉得可以把上下文管理分为3个阶段
第一个阶段是,请求进来后把请求相关数据封装到local对象中,
第二个阶段是:在视图函数中,视图函数从local对象中取出请求相关的数据
第三个阶段:请求结束后,把local对象中的相关数据进行销毁,
如果说的更细一点:
请求进来之后:
        首先对封装了两个对象,其中一个叫RequestContext,里边封装了request和session,另一个叫appcontext,里边封装了app和g,,接下来基于LocalStack栈把这两个对象分别放到两个local的对象中存放起来,这个local对象类似于threading.local,但是他的粒度更细一下,基于协程来做的 用来保存和隔离每次的请求数据
视图阶段:
        是基于LocalProxy类,localProxy 调用偏函数partial,通多partial再调用LOcalStack这样就可以把存在Local对象中的数据取出来.
请求结束:
先把local中的session写到cookie中,然后再 调用LocalStack.pop把local中的数据pop掉

 

转载于:https://www.cnblogs.com/sticker0726/p/8962821.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值