当一个请求到来时,浏览器会携带很多信息发送发送服务端。在Django中,每一个处理函数都要传入一个request的参数,该参数携带所有请求的信息,也就是服务端程序封装的environ(不明白该参数可以参见上一篇flask初探之WSGI)。简单示例如下

from django.shortcuts import render

def index(request):
context = {}
return render(request, "index.html", context)

每一个请求携带的数据都可以从request传入到处理函数中,这种处理方法可以称之为显示传递。

接收请求数据在Flask中有一种更巧妙的实现:当有请求到来时request就会变成一个全局变量,所有的处理函数可以直接使用request这个全局变量,而不需要显示传入参数。简单示例如下:

import time
from flask import Flask, request app = Flask(__name__) @app.route('/')
def hello_world():
return 'Hello '+request.args.get("name")

这种设计减少了每个函数需要传入的参数,比起Django的显示传参更加优雅。

但是这种全局变量也会自己的问题,多线程的情况下同一时间能够处理多个请求,每个处理函数都需要自己的请求信息,如何保证处理函数和请求一一对应呢?Flask主要使用本地线程技术来保证请求信息和处理函数相互的对应。下面主要介绍本地线程技术。

本地线程

在多线程编程中,全局变量不可避免的会竞争,通常使用加锁来解决竞争。此外有一种本地线程技术可以让每一个线程都拥有自己的私有的变量。比如全局变量a,使用本地线程技术可以让每一个线程对a处理时都是互相隔离的,彼此之间不影响。下面从局部变量、全局变量和本地线程三个例子对比说明本地线程技术。

局部变量

开启多线程,每个子线程完成不同的计算任务,x是线程中的局部变量。

每个子线程都有独立的空间。每次压栈,局部变量x的作用域地址是不同的(线程独享),计算结果互不干扰。

import time
import threading def worker():
x = 0
for i in range(100):
time.sleep(0.0001)
x += 1
print(threading.current_thread(),x) for i in range(10):
threading.Thread(target=worker).start()

运行结果:

<Thread(Thread-2, started 123145372971008)> 100
<Thread(Thread-6, started 123145393991680)> 100
<Thread(Thread-1, started 123145367715840)> 100
<Thread(Thread-3, started 123145378226176)> 100
<Thread(Thread-5, started 123145388736512)> 100
<Thread(Thread-7, started 123145399246848)> 100
<Thread(Thread-4, started 123145383481344)> 100
<Thread(Thread-10, started 123145415012352)> 100
<Thread(Thread-8, started 123145404502016)> 100
<Thread(Thread-9, started 123145409757184)> 100

全局变量

当多线程使用全局变量时就会发生抢占和竞争

import threading
import time x = 0
def worker():
global x
x = 0
for i in range(100):
time.sleep(0.0001)
x += 1
print(threading.current_thread(),x) for i in range(10):
threading.Thread(target=worker).start()

运行结果:

<Thread(Thread-2, started 123145483571200)> 888
<Thread(Thread-5, started 123145499336704)> 908
<Thread(Thread-3, started 123145488826368)> 930
<Thread(Thread-4, started 123145494081536)> 937
<Thread(Thread-1, started 123145478316032)> 941
<Thread(Thread-6, started 123145504591872)> 947
<Thread(Thread-7, started 123145509847040)> 949
<Thread(Thread-8, started 123145515102208)> 955
<Thread(Thread-9, started 123145520357376)> 962
<Thread(Thread-10, started 123145525612544)> 964

希望的结果是100,最后却远大于100。原因在于第一个线程将全局变量+1之后,第二个线程在这个基础上继续+1,第三个线程在继续对x+1,每个线程都对全局变量+1,最终结果就不符合预期。

本地线程

本地线程可以避免上面全局变量竞争问题。标准库threading中就自带本地线程对象。

import time
import threading a = threading.local() # 全局对象 def worker():
a.x = 0
for i in range(100):
time.sleep(0.0001)
a.x += 1
print(threading.current_thread(),a.x) for i in range(10):
threading.Thread(target=worker).start()

运行结果:

<Thread(Thread-4, started 123145570172928)> 100
<Thread(Thread-6, started 123145580683264)> 100
<Thread(Thread-1, started 123145554407424)> 100
<Thread(Thread-2, started 123145559662592)> 100
<Thread(Thread-8, started 123145591193600)> 100
<Thread(Thread-5, started 123145575428096)> 100
<Thread(Thread-3, started 123145564917760)> 100
<Thread(Thread-7, started 123145585938432)> 100
<Thread(Thread-10, started 123145601703936)> 100
<Thread(Thread-9, started 123145596448768)> 100

本质上本地线程对象就是一个字典的子类,为每一个线程创建一个键值对,key是线程id,value是值。当某一个线程操作变量时就是操作自己的id对象的值。

如上例中本地线程是a,可将其看做一个字典a = {"线程id": x}。线程1中a={"123145570172928":44},线程2中a={"123145559662592": 55}。所以各个线程之间虽然引用了同名变量,但实际上是互相不干扰的。

LocalStack

本地栈和本地线程类似的功能,本地线程常用来处理数字或字符串等简单数据结构,维护了{"线程id":值}这样一个关系。本地栈是一个可以当做栈来使用的结构,本质上也是一个字典,结构为{"线程id":{"stack":[]}。这个数据结构的主要是能够使用压栈和出栈等操作,方便先进后出的场景。

简单使用

import time
from werkzeug.local import LocalStack local_stack = LocalStack()
local_stack.push("abc")
local_stack.push("xyz") # 获取栈顶元素,不弹出元素
print(local_stack.top) # 弹出栈顶元素,出栈
print(local_stack.pop()) # 再次获取栈顶,栈顶元素已变化
print(local_stack.top)

运行结果:

xyz
xyz
abc

线程互不干扰

import threading
from werkzeug.local import LocalStack def worker(local_stack):
print(local_stack.top) # 主线程中压栈了数据,但是在子线线程中取不到,线程互相隔离。 if __name__ == "__main__":
local_stack = LocalStack()
local_stack.push("主线程") threading.Thread(target=worker, args=(local_stack,)).start()
print(local_stack.top) 运行结果:
None
主线程

request的线程隔离实现

通过本地线程技术,request虽然是全局变量,但是在每一个线程中都是互相隔离的。

但需要说明的是Flask中并不是使用标准线程库的本地线程对象,因为还需要兼容协程,所以flask使用了werkzeug中的本地线程对象werkzeug.local.Local()。werkzeug的本地线程对象增加了对Greenlet的优先支持。

werkzeug中本地线程的实现

# since each thread has its own greenlet we can just use those as identifiers
# for the context. If greenlets are not available we fall back to the
# current thread ident depending on where it is.
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident class Local(object):
__slots__ = ("__storage__", "__ident_func__") def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident) def __iter__(self):
return iter(self.__storage__.items()) def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy) def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None) def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name) def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value} def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

从import可以看出,首先是从协程导入,如果报错再从线程导入。在__setattr__函数添加变量时,首先是通过get_ident方法获取了线程id,然后将线程id作为key,value又是一个字典{name:value}。类似于{"线程id":{"name": "value"}}。

Flask中请求数据的优雅传递的更多相关文章

  1. python web开发-flask访问请求数据request

    Request对象在web应用的开发中是一个非常重要的对象,主要用来获取用户发来的请求数据. 常用属性参考:http://docs.jinkan.org/docs/flask/api.html#fla ...

  2. Flask 中字典数据返回(jsonify)

    不多说,直接上代码,flask中的字典数据的处理: from flask import Flask, jsonify app = Flask(__name__, static_folder=" ...

  3. js中请求数据的$post和$ajax区别(同步和异步问题)

    $.post和$.Ajax都为页面上向后台发送请求,请求数据 1.post 因为post默认为异步请求,可是有时候我们会发现,本来要求请求马上出现,可是异步会导致后面突然再执行,这样就出很多问题 2. ...

  4. Nuxt.js 如何在 asyncData中 请求数据 ,并将拿到的数据传给子组件

    说明:同接口请求一样,也可以进行数据的处理:return  中 左侧的变量  可以直接拿到在页面上使用,也可以传递给子组件 下面再给出一段代码,方便觉得有用的.却又不想手敲的朋友们: async as ...

  5. flask中的数据操作

    flask中数据访问: pip install flask-sqlalemy 创建数据: 创建app的工厂 from flask import Flask from flask_sqlalchemy ...

  6. flask中请求勾子

    请求勾子 在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如: *在请求开始时,建立数据库连接; *在请求开始时,根据需求进行权限校验; *在请求结束时,指定数据的交互格式; 为了让每 ...

  7. C# 中请求数据方式

    #region 根据URL获取结果集        /// <summary>        /// 根据URL获取结果集 默认为GET,如果数据量大了可以传入POST        // ...

  8. vue.js中请求数据v-for循环使用数据

    1.效果图 2.cart.json { "message":"", "status":"1", "result ...

  9. struts中请求数据自动封装

    实现原理 参数拦截器 第一:jsp表单数据填充到action中的属性 必须实现set方法,get方法可以不需要实现,jsp页面name名字保持一致 第二:jsp表单填充到action的对象的属性 js ...

随机推荐

  1. vim - 显示不可见字符(:set list)

    默认情况下,vim是不会显示space,tabs,newlines,trailing space,wrapped lines等不可见字符的.我们可以使用以下命令打开list选项,来显示非可见字符: : ...

  2. HDU分拆素数和

    https://acm.hdu.edu.cn/showproblem.php?pid=2098 时间复杂度 #include<bits/stdc++.h> using namespace ...

  3. MATLAB中插值算法实现

    %%%1.M文件%(1).以往少的程序可以在命令行窗口进行编码,但大量的程序编排到命令行窗口,%会有造成乱码的危险.(2).如果将命令编成程序存储在一个文件中(M文件),依次运行文件中的命令,则可以重 ...

  4. 在Linux系统(centos7)中,安装VScode,并在VScode上编写HTML网页

    [实验目的] 在Linux系统中,搭建编写HTML网页的环境.在VS code官网上,下载VS code安装程序,进行安装.在VS code软件中编写HTML页面,并正确运行. [实验步骤] 1)   ...

  5. 【体验】在Adobe After Effects CC 2018中使用脚本创建窗口

    1.主界面 2.脚本编辑器主界面 3.对象浏览器 在脚本编辑器中按F1 4.写一段 ScriptUI var win = new Window('window', 'my win', [100, 10 ...

  6. spring内嵌cglib包,这里藏着一个大坑

    问题发现 2022-01-21 早上 9 点,订单系统出现大面积的"系统未知错误"报错,导致部分用户无法正常下单.查询后台日志,可以看到大量的 duplicate class at ...

  7. 3D建模服务提供更高效、专业的能力,“筑”力开发者

    3D建模服务(3D Modeling Kit)是HMS Core在图形图像领域又一技术开放.3D建模产品的定位就是要做快速.简洁.低成本的3D制作能力,并陆续开放给有3D模型.动画游戏制作等能力诉求的 ...

  8. APC 篇——备用 APC 队列

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. 【ASP.NET Core】使用最熟悉的Session验证方案

    如果大伙伴们以前写过 ASP 或 PHP 之类的,相信各位对基于 Session 的身份验证很熟悉(其实在浏览器端是结合 Cookie 来处理的).这种验证方式是比较早期的,操作起来也不复杂. a.用 ...

  10. docker内存限制

    默认docker容器可以使用宿主机所有的内存和CPU,我们可以通过 docker run 来限制内存和CPU的使用. 有的系统内核不支持 Linux capabilities. 可以通过 docker ...