flask之分析线程和协程

01 思考:每个请求之间的关系

我们每一个请求进来的时候都开一个进程肯定不合理,那么如果每一个请求进来都是串行的,那么根本实现不了并发,所以我们假定每一个请求进来使用的是线程。

那么线程中数据互相不隔离,存在修改数据的时候数据不安全的问题。

假定我们的需求是,每个线程都要设置值,并且该线程打印该线程修改的值。

from threading import Thread,current_thread
import time class Foo(object):
def __init__(self):
self.name = 0 locals_values = Foo() def func(num):
locals_values.name = num
time.sleep(2) # 取出该线程的名字
print(locals_values.name, current_thread().name) for i in range(10):
# 设置该线程的名字
t = Thread(target=func,args=(i,),name='线程%s'%i)
t.start()

很明显阻塞了2秒的时间所有的线程都完成了修改值,而2秒后所有的线程打印出来的时候都是9了,就产生了数据不安全的问题。

所以我们要解决这种线程不安全的问题,有如下两种解决方案。

  • 方案一:是加锁

  • 方案二:使用threading.local对象把要修改的数据复制一份,使得每个数据互不影响。

    我们要实现的并发是多个请求实现并发,而不是纯粹的只是修改一个数据,所以第二种思路更适合做我们每个请求的并发,把每个请求对象的内容都复制一份让其互相不影响。

    详解:为什么不用加锁的思路?加锁的思路是多个线程要真正实现共用一个数据,并且该线程修改了数据之后会影响到其他线程,更适合类似于12306抢票的应用场景,而我们是要做请求对象的并发,想要实现的是该线程对于请求对象这部分内容有任何修改并不影响其他线程。所以使用方案二

02 threading.local

多个线程修改同一个数据,复制多份数据给每个线程用,为每个线程开辟一块空间进行数据存储

实例:

from threading import Thread,current_thread,local
import time locals_values = local()
# 可以简单理解为,识别到新的线程的时候,都会开辟一片新的内存空间,相当于每个线程对该值进行了拷贝。 def func(num):
locals_values.name = num
time.sleep(2)
print(locals_values.name, current_thread().name) for i in range(10):
t = Thread(target=func,args=(i,),name='线程%s'%i)
t.start()

如上通过threading.local实例化的对象,实现了多线程修改同一个数据,每个线程都复制了一份数据,并且修改的也都是自己的数据。达到了我们想要的效果。

03 通过字典自定义threading.local

实例:

from threading import get_ident,Thread,current_thread
# get_ident()可以获取每个线程的唯一标记,
import time class Local(object):
storage = {}# 初始化一个字典
get_ident = get_ident # 拿到get_ident的地址
def set(self,k,v):
ident =self.get_ident()# 获取当前线程的唯一标记
origin = self.storage.get(ident)
if not origin:
origin={}
origin[k] = v
self.storage[ident] = origin
def get(self,k):
ident = self.get_ident() # 获取当前线程的唯一标记
v= self.storage[ident].get(k)
return v locals_values = Local()
def func(num):
# get_ident() 获取当前线程的唯一标记
locals_values.set('KEY',num)
time.sleep(2)
print(locals_values.get('KEY'),current_thread().name) for i in range(10):
t = Thread(target=func,args=(i,),name='线程%s'%i)
t.start()

讲解:

利用get_ident()获取每个线程的唯一标记作为键,然后组织一个字典storage。

:{线程1的唯一标记:{k:v},线程2的唯一标记:{k:v}.......}


15088: {'KEY': 0},
8856: {'KEY': 1},
17052: {'KEY': 2},
8836: {'KEY': 3},
13832: {'KEY': 4},
15504: {'KEY': 5},
16588: {'KEY': 6},
5164: {'KEY': 7},
560: {'KEY': 8},
1812: {'KEY': 9}
}

运行效果

04 通过setattr和getattr实现自定义threthreading.local

实例

from threading import get_ident,Thread,current_thread
# get_ident()可以获取每个线程的唯一标记,
import time class Local(object):
storage = {}# 初始化一个字典
get_ident = get_ident # 拿到get_ident的地址 def __setattr__(self, k, v):
ident =self.get_ident()# 获取当前线程的唯一标记
origin = self.storage.get(ident)
if not origin:
origin={}
origin[k] = v
self.storage[ident] = origin
def __getattr__(self, k):
ident = self.get_ident() # 获取当前线程的唯一标记
v= self.storage[ident].get(k)
return v locals_values = Local()
def func(num):
# get_ident() 获取当前线程的唯一标记
locals_values.KEY=num
time.sleep(2)
print(locals_values.KEY,current_thread().name) for i in range(10):
t = Thread(target=func,args=(i,),name='线程%s'%i)
t.start()

05 每个对象有自己的存储空间(字典)

我们可以自定义实现了threading.local的功能,但是现在存在一个问题,如果我们想生成多个Local对象,但是会导致多个Local对象所管理的线程设置的内容都放到了类属性storage = {}里面,所以我们如果想实现每一个Local对象所对应的线程设置的内容都放到自己的storage里面,就需要重新设计代码。

实例:

from threading import get_ident,Thread,current_thread
# get_ident()可以获取每个线程的唯一标记,
import time class Local(object):
def __init__(self):
# 千万不要按照注释里这么写,否则会造成递归死循环,死循环在__getattr__中,不理解的话可以全程使用debug测试。
# self.storage = {}
# self.get_ident =get_ident
object.__setattr__(self,"storage",{})
object.__setattr__(self,"get_ident",get_ident) # 借用父类设置对象的属性,避免递归死循环。 def __setattr__(self, k, v):
ident =self.get_ident() # 获取当前线程的唯一标记
origin = self.storage.get(ident)
if not origin:
origin={}
origin[k] = v
self.storage[ident] = origin
def __getattr__(self, k):
ident = self.get_ident() # 获取当前线程的唯一标记
v= self.storage[ident].get(k)
return v locals_values = Local()
locals_values2 = Local()
def func(num):
# get_ident() 获取当前线程的唯一标记
# locals_values.set('KEY',num)
locals_values.KEY=num
time.sleep(2)
print(locals_values.KEY,current_thread().name)
# print('locals_values2.storage:',locals_values2.storage) # 查看locals_values2.storage的私有的storage for i in range(10):
t = Thread(target=func,args=(i,),name='线程%s'%i)
t.start()

显示效果我就不做演示了,和前几个案例演示效果一样。

06 如果是你会如何设计flask的请求并发?

  • 情况一:单进程单线程,基于全局变量就可以做

  • 情况二:单进程多线程,基于threading.local对象做

  • 情况三:单进程多线程多协程,如何做?

    提示:协程属于应用级别的,协程会替代操作系统自动切换遇到 IO的任务或者运行级别低的任务,而应用级别的切换速度远高于操作系统的切换

    当然如果是自己来设计框架,为了提升程序的并发性能,一定是上诉的情况三,不光考虑多线程并且要多协程,那么该如何设计呢?

    在我们的flask中为了这种并发需求,依赖于底层的werkzeug外部包,werkzeug实现了保证多线程和多协程的安全,werkzeug基本的设计理念和上一个案例一致,唯一的区别就是在导入的时候做了一步处理,且看werkzeug源码。

    werkzeug.local.py部分源码

    ...
    
    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):
    ... def __init__(self):
    object.__setattr__(self, '__storage__', {})
    object.__setattr__(self, '__ident_func__', get_ident) ... 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}

    分析:

    原理就是在最开始导入线程和协程的唯一标识的时候统一命名为get_ident,并且先导入协程模块的时候如果报错说明不支持协程,就会去导入线程的get_ident,这样无论是只有线程运行还是协程运行都可以获取唯一标识,并且把这个标识的线程或协程需要设置的内容都分类存放于__storage__字典中。

flask之分析线程和协程的更多相关文章

  1. Flask 之分析线程和协程

    目录 flask之分析线程和协程 01 思考:每个请求之间的关系 02 threading.local 03 通过字典自定义threading.local 04 通过setattr和getattr实现 ...

  2. python自动化开发-[第十天]-线程、协程、socketserver

    今日概要 1.线程 2.协程 3.socketserver 4.基于udp的socket(见第八节) 一.线程 1.threading模块 第一种方法:实例化 import threading imp ...

  3. Python进阶(5)_进程与线程之协程、I/O模型

    三.协程 3.1协程概念 协程:又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存 ...

  4. Python学习笔记整理总结【网络编程】【线程/进程/协程/IO多路模型/select/poll/epoll/selector】

    一.socket(单链接) 1.socket:应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socke ...

  5. Golang:线程 和 协程 的区别

    作者:林冠宏 / 指尖下的幽灵 博客:http://www.cnblogs.com/linguanh/ GitHub : https://github.com/af913337456/ 掘金:http ...

  6. 深入分析 Java、Kotlin、Go 的线程和协程

    前言 协程是什么 协程的好处 进程 进程是什么 进程组成 进程特征 线程 线程是什么 线程组成 任务调度 进程与线程的区别 线程的实现模型 一对一模型 多对一模型 多对多模型 线程的"并发& ...

  7. Python学习之路--进程,线程,协程

    进程.与线程区别 cpu运行原理 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Q ...

  8. Python—进程、线程、协程

    一.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 方法: ...

  9. 分析Tornado的协程实现

    转自:http://www.binss.me/blog/analyse-the-implement-of-coroutine-in-tornado/ 什么是协程 以下是Wiki的定义: Corouti ...

随机推荐

  1. 命令序列 ; & && ||

    ; 从左到右依次被执行,返回最后一个命令的执行状态 & 该命令将在后台被执行,即在子bash中执行(或ctrl+z,bg, jobs,bg 命令号)(变量$!.$one.$two.$three ...

  2. 【译】利用Lombok消除重复代码

    当你在写Getter和Setter时,一定无数次的想过,为什么会有POJO这么烂的东西.你不是一个人!(不是骂人-)无数的开发人员花费了大量的时间来写这种样板代码,而他们本来可以利用这些时间做出更有价 ...

  3. SQL Server设计三范式

    第一范式(1NF) (必须有主键,列不可分) 数据库表中的任何字段都是单一属性的,不可再分 create table aa(id int,NameAge varchar(100)) insert aa ...

  4. 读取JDK API文档,并根据单词出现频率排序

    1,拿到 API 文档 登录 https://docs.oracle.com/javase/8/docs/api/ , 选中特定的类,然后 copy 其中的内容, 放入 TXT 文件中 , 2,读取T ...

  5. 使用boost data_time模块来获取毫秒级时间并转换为string字符串

    本文首发于个人博客https://kezunlin.me/post/107cbcbb/,欢迎阅读! using boost.date_time to get time in millisecond m ...

  6. 初探three.js光源

    上一节我们简单的说了一下THREE中必要的元素.今天我们深入探讨一下各种THREE的光源. 一 基础光源 基础光源有4种1.THREE.AmbientLight(环境光源)2.THREE.PointL ...

  7. Python 0基础开发游戏:打地鼠(详细教程)VS code版本

    如果你没有任何编程经验,而且想尝试一下学习编程开发,这个系列教程一定适合你,它将带你学习最基本的Python语法,并让你掌握小游戏的开发技巧.你所需要的,就是付出一些时间和耐心来尝试这些代码和操作. ...

  8. SpringBoot:带你认认真真梳理一遍自动装配原理

    前言 Spring翻译为中文是“春天”,的确,在某段时间内,它给Java开发人员带来过春天,但是随着我们项目规模的扩大,Spring需要配置的地方就越来越多,夸张点说,“配置两小时,Coding五分钟 ...

  9. Hbase初识

    简介 数据模型 相关数据库 典型应用 优势 劣势 key-value Redis 缓存 快速查询 存储数据缺乏结构化 列族 Cassandra,Hbase 分布式的文件系统,大规模的数据存储 易于分布 ...

  10. 深入浅出Spring(三)

    我为大家简单介绍了一下Spring框架核心内容中的IoC,接下来我们继续讲解另一个核心AOP(Aspect Oriented Programming),即面向切面编程. 1.OOP回顾 在介绍AOP之 ...