为什么用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()
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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()
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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,为线程开辟独有的空间来保存值
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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()
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
线程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__ 这样就显得有逼格多了
第三种代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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()
结果:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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中提供了两种上下文管理机制
一个是请求上下文,另一个应用上下文
从源码中来说: 上下文管理流程分为三个阶段