flask源码分析-上下文机制(五)

什么是上下文

所谓上下文,可以认为是一个程序运行时所需要的环境,比如一些变量,数据的保存。
比如当你的同事在开培训会的时候,你迟到了,如果别人已经讲了半天,你半路插进来,你可能接下来的时间就完全听不懂别人在讲什么了,因为你缺少了上下文环境,这就叫脱离上下文。
在flask框架中也是这样,将程序运行所需要的数据封装到一个对象中,以便你在程序中各个位置都可以调用。

如何使用上下文

为了能让你再程序的各个位置都能随时调用这些数据,有两种做法

  • 直接把这个对象当做参数传入到你需要执行的函数中,做成局部变量,比如django框架是这种做法,这样做避免了多线程访问时数据错乱问题,但是很麻烦啊。
def view1(request):
    request.xxx # 调用request对象
    return 'response'

def view2(request):
    request.xxx  # 调用request对象
    return 'response'
  • 做成全局变量,你使用起来会更随意,但是会带来一个问题,当多个线程或协程都来修改这些全局变量时,因为线程是抢占式的,而且进程下的线程间共享全局变量那么如何保证线程安全??
from flask import request

@app.route('/')
def view1():
    request.xxx # 调用request对象
    return 'response'

多线程修改全局变量造成数据被其他线程篡改

以下是一个用户存钱的案例,有一个存钱的函数,
每个线程过来会调用这个函数,将本线程存的钱数当做参数传给存钱函数,
函数会打印出每个用户存多少钱
单线程调用时不会产生任何问题,你传入多少前就存入多少钱
多线程修改这个全局对象时会产生意想不到的情况,当然你会想到加锁,加锁由并发变成了串行,势必造成效率减慢,这不是框架所追求的。

from threading import Thread
import time

class Request:
    def __init__(self):
        self.money = None

request = Request()

def save_money(i):
    request.money = i
    time.sleep(0.001) # 因为线程运行非常快,可以尝试修改睡眠时间观察money的值如何变化
    print('我是用户%d' % i, '我存入了%d元' % request.money)

# 单线程调用
# save_money(2)

# 多线程调用
for i in range(20):
    t = Thread(target=save_money, args=(i,))
    t.start()

如何将各个线程数据隔离开来

我们都有这样的经历:当有很多人给你布置任务的时候,你很有可能非常忙乱,要么是遗漏了某个任务,要么是将某两个人的任务弄混了,如果此时你有一支笔,一张纸,你记录了一张名单,需要的时候查一下这张名单,是不是就不会乱了?
那么此时我们的想法是这样的:
每个线程不是有一个线程标识嘛,那么我利用这个线程标识维护一个字典,把线程标识当做key,把需要保存的数据当做value,不就可以了吗?
我们来实现一个类,这个类叫做仓库,仓库有一个个编号,每个编号内装着对应数据
在这里插入图片描述
代码实现:

from threading import Thread
import time


from _thread import get_ident
class Local:

    def __init__(self):
        object.__setattr__(self, 'ident', get_ident)
        object.__setattr__(self, 'storage', {})

    def __setattr__(self, key, value):
        ident = self.ident()
        data = self.storage.get(ident)
        if not data:
            data = {key: value}
        else:
            data[key] = value
        self.storage[ident] = data

    def __getattr__(self, item):
        ident = self.ident()
        data = self.storage.get(ident)
        if not data:
            return None
        return data.get(item, None)

# class Request:
#     def __init__(self):
#         self.money = None

# request = Request()
request = Local()
def save_money(i):
    request.money = i
    time.sleep(0.001) # 因为线程运行非常快,可以尝试修改睡眠时间观察money的值如何变化
    print('我是用户%d' % i, '我存入了%d元' % request.money)

# 单线程调用
# save_money(2)

# 多线程调用
for i in range(20):
    t = Thread(target=save_money, args=(i,))
    t.start()

flask请求上下文和应用上下文

现在可以保证request这个全局变量的线程安全了,但是flask之提供了这一个全局变量吗?其实不是,有以下几个:request, session, g, session, current_app,如果你使用了其他flask插件,他们也会相应的提供一些全局对象,比如flask_login中的current_user
flask会再进行一些封装,将这些全局对象封装到上下文对象中

  • 请求上下文封装:
    • app
    • request
    • session
  • 应用上下文封装
    • app
    • g

大家想一下,我们使用request对象时一般在哪个位置调用,是不是都是在视图函数中?我们在视图函数中使用request对象时考虑过线程安全问题吗?如果没有的话,那么肯定是flask在视图函数分发之前将这个request对象隔离起来了,接下来思考一下如何放置

刚才我们已经把仓库建好了,我觉得我现在需要创建一个仓库管理员类,这个类负责帮我在请求到来时把上下文放到仓库,请求结束时帮我把上下文销毁掉。类似这样:
在这里插入图片描述
flask请求流程
在这里插入图片描述

还记得上次我们找到的flask程序运行入口吗?我把flask核心代码拿出来,就下面这十几行

ctx = self.request_context(environ)
error = None
try:
	try:
		ctx.push()
		response = self.full_dispatch_request()
	except Exception as e:
		error = e
		response = self.handle_exception(e)
	except:
		error = sys.exc_info()[1]
		raise
	return response(environ, start_response)
finally:
if self.should_ignore_error(error):
	error = None
ctx.auto_pop(error)

什么情况下需要使用上下文

flask上下文的仓库存储的其实是一个栈结构,上面我为了讲明白把他简化了,因为flask其实是可以写多个app应用的
经过剖析源码我发现只有在执行视图函数时中flask才会帮你把上下文推入栈,在其他情况下需要你手动推入栈,比如你编写一个离线脚本进行项目测试时
比如我想查看一下app的名字?

from test import app
from flask import current_app

# 比如你想通过这个current_app这个全局对象查看一下当前app的名字
with app.app_context():
    print(current_app.name)

又比如你想往数据库中插入一下数据,但是数据库的配置是写在app的config当中的,此时你也需要推一个应用上下文入栈

贯穿flask上下文机制的三个类

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值