Flask 之分析线程和协程

目录

flask之分析线程和协程

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

Flask 之分析线程和协程

我们每一个请求进来的时刻都开一个历程一定不合理,那么若是每一个请求进来都是串行的,那么基本实现不了并发,以是我们假定每一个请求进来使用的是线程。

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

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

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了,就产生了数据不平安的问题。

Flask 之分析线程和协程

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

  • 方案一:是加锁

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

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

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

02 threading.local

Flask 之分析线程和协程

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

实例:

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

Flask 之分析线程和协程

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

03 通过字典自定义threading.local

Flask 之分析线程和协程

实例:

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}
                    }

运行效果

Flask 之分析线程和协程

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

Flask 之分析线程和协程

实例

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 每个工具有自己的存储空间(字典)

Flask 之分析线程和协程

我们可以自定义实现了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的请求并发?

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__字典中。

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/1944.html