Flask 上下文管理

时间:2022-01-19 12:42:50

threading.local

为什么用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

这样就造成了数据不安全,本来我们设想的是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

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

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()

结果:

线程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

让我们来继续优化下,注意我们在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

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

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

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

上下文管理机制

什么是上下文?

我们可以把上下文理解为当前环境的快照.和阅读文章时的上下文一样,如果在阅读文章时,单独取出一句话让你理解,你也许会摸不到头脑,但是放到文章中就可以了.

设计思想:上下文:有一些参数在对象的外部,我们把这些参数赋值到对象中形成新的对象,这个新的对象就叫做上下文。Flask 上下文管理

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

一个是请求上下文(request context),另一个应用上下文(application context)

上下文流程

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

我觉得可以把上下文管理分为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掉.
为了方便获取这两种上下文的信息,flask提供了四个变量
Flask 上下文管理

激活上下文

当请求进入时,Flask会自动激活请求上下文,这时候我们可以使用request和session变量,另外程序上下文也会被激活,当请求处理完毕时,这两种上下文都会自动销毁,也就是他俩的生命周期是一样的.

如果我们在没有激活上下文时,使用这些变量,flask就会抛出异常Runingtimeerror ,working outside of application context.
同样依赖于上下文的还有url_for()函数,和jsonify()函数,只能 在视图函数中使用它.
手动激活上下文
如果你要在没有激活上下文的情况下使用这些变量可以手动激活上下文
程序上下文使用app.app_context()获取,我们可以使用with语句执行上下文操作
from app import app
from flask import current_app
with app.app_context():
print(current_app.name)

在源码中这样体现

Flask 上下文管理

或者是显示地使用push方法激活上下文,在执行完相关操作时使用pop()方法销毁上下文:

from  app import  app
from flask import current_app app_ctx = app.app_context()
app.ctx.push()
print(current_app.name)

而请求上下文可以通过text_request_context()方法临时创建

from app import app
from flask import request with app.test_request_context('/hello'):
print(request.method)
 
这个在测试应用程序的时候用的比较多,详情参见http://www.bjhee.com/flask-ad9.html

上下文钩子

flask也为上下文提供了一个teardown_appcontext钩子,使用它注册的回调函数会在程序上下文被销毁或请求上下文被销毁时调用,比如你需要在每个请求处理结束后销毁数据库连接

@app.teardown_appcontext
def teardown_db(exception)
db.close() #使用该装饰器注册的回调函数需要接受异常对象作为参数,当请求被正常处理的时候,这个参数值将是None,这个函数的返回值将被忽略.
localstack、local、字典之间的关系?
local是使用了字典的方式实现线程隔离,localStack把local对象作为他自己的一个属性,来作为线程隔离的一个栈结构。
 

flask使用线程隔离的意义?

意义在于:使用当前线程能够正确引用到他自己所创建的对象,而不是引用到其他线程所创建的对象。这就是线程隔离的意义

体会:封装如果一次封装解决不了问题,那么就再来一次。
 
 
 

Flask 上下文管理的更多相关文章

  1. Flask上下文管理、session原理和全局g对象

    一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...

  2. Flask上下文管理

    一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...

  3. Flask上下文管理机制

    前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...

  4. Flask上下文管理及源码刨析

    基本流程概述 - 与django相比是两种不同的实现方式. - django/tornado是通过传参数形式实现 - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...

  5. flask 上下文管理 &源码剖析

    基本流程概述 - 与django相比是两种不同的实现方式. - django/tornado是通过传参数形式实现 - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了. - 上 ...

  6. Flask 上下文管理-- (session,request,current_app的传递)--类似本地线程实现,以及多app应用

    Flask session,request,current_app的传递 请求上下文的作用 -- 封装请求相关得数据(request,session) 请求上下文 request session re ...

  7. Flask上下文管理机制流程(源码剖析)

    Flask请求上下文管理 1 偏函数 partial 使用该方式可以生成一个新函数 from functools import partial def mod( n, m ): return n % ...

  8. Flask - 上下文管理(核心)

    参考 http://flask.pocoo.org/docs/1.0/advanced_foreword/#thread-locals-in-flask https://zhuanlan.zhihu. ...

  9. python - Flask 上下文管理 流程

    上下文管理:    - 请求上下文 (ctx=RequestContext())  : request/session    - App上下文  (app_ctx=AppContext())  : a ...

随机推荐

  1. C#客户端的异步操作

    上篇博客[用Asp.net写自己的服务框架] 我讲述了如何实现自己的服务框架,但我想很多人应该用过WebService这类服务框架,相比起来,似乎还缺少什么东西, 是的,我也感觉到了.比如:我可以很容 ...

  2. OS X 在Cisco无线环境下丢包分析 part 2

    part 1说到,单播的ARP请求最终都被网关丢弃了,从而造成了丢包.先说我最终怎么解决的吧,我最终把核心交换上针对无线VLAN的arp inspection和dhcp snooping删掉了,然后出 ...

  3. SQL Server Reporting Services 自定义数据处理扩展DPE(Data Processing Extension)

    最近在做SSRS项目时,遇到这么一个情形:该项目有多个数据库,每个数据库都在不同的服务器,但每个数据库所拥有的数据库对象(table/view/SPs/functions)都是一模一样的,后来结合网络 ...

  4. Tinyproxy

    Tinyproxy Tinyproxy is a light-weight HTTP/HTTPS proxy daemon for POSIX operating systems. Designed ...

  5. 跳舞链 Dancing Links

    作为搜索里面的一个大头,终于刷了一部分题目了,跳舞链一般都有现成的模板来套...... 至于跳舞链的学习的话,我觉得http://www.cnblogs.com/grenet/p/3163550.ht ...

  6. Linux环境下的GCC编译器与GDB调试工具介绍

    假如现在我们有如下代码需要编译运行和调试.文件名为:test.c #include <stdio.h> int main() { int day, month, year, sum, le ...

  7. &lbrack;js高手之路&rsqb;使用原型对象&lpar;prototype&rpar;需要注意的地方

    我们先来一个简单的构造函数+原型对象的小程序 function CreateObj( uName, uAge ) { this.userName = uName; this.userAge = uAg ...

  8. mac 上安装服务,查看服务,重启和关闭

    首先了解下的Mac的 homebrew ,官网:https://brew.sh/index_zh-cn.html 简单的说: Homebrew 能干什么? 答:使用 Homebrew 安装 Apple ...

  9. 《java入门第一季》之面向对象(形式参数和返回值问题的深入研究3)

    /*     形式参数:         引用类型 接口:需要的是该接口的实现类对象 这个时候就没什么了,和抽象类的解释差不多. */ interface Love { public abstract ...

  10. SpriteBuilder中关节的Breaking force属性

    在SpriteBuilder中三种物理关节都包含Breaking force区域在属性框中. 该属性被设置成关节可以承受的压力临界值.如果关节的压力超出了Breaking force中设置的值,则关节 ...