flask之分析线程和协程
flask之分析线程和协程
思考:每个请求之间的关系
我们每一个请求进来的时候都开一个进程肯定不合理,那么如果每一个请求进来都是串行的,那么根本实现不了并发,所以我们假定每一个请求进来使用的是线程。
那么线程中数据互相不隔离,存在修改数据的时候数据不安全的问题。
假定我们的需求是,每个线程都要设置值,并且该线程打印该线程修改的值。
import time from threading import Thread def task(arg): global a a = arg time.sleep(1) print(a) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
很明显阻塞了2秒的时间所有的线程都完成了修改值,而2秒后所有的线程打印出来的时候都是9了,就产生了数据不安全的问题。
所以我们要解决这种线程不安全的问题,有如下两种解决方案。
-
方案一:是加锁
-
方案二:使用
threading.local
对象把要修改的数据复制一份,使得每个数据互不影响。我们要实现的并发是多个请求实现并发,而不是纯粹的只是修改一个数据,所以第二种思路更适合做我们每个请求的并发,把每个请求对象的内容都复制一份让其互相不影响。
详解:为什么不用加锁的思路?加锁的思路是多个线程要真正实现共用一个数据,并且该线程修改了数据之后会影响到其他线程,更适合类似于12306抢票的应用场景,而我们是要做请求对象的并发,想要实现的是该线程对于请求对象这部分内容有任何修改并不影响其他线程。所以使用方案二
threading.local
多个线程修改同一个数据,复制多份数据给每个线程用,为每个线程开辟一块空间进行数据存储
用函数实现
import time from threading import Thread,local,get_ident a = local() def task(arg): a.value = arg time.sleep(1) print(a.value) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
升级
import time from threading import Thread,local,get_ident storage = {} def set(k,v): # 获取线程id ident = get_ident() if ident in storage: storage[ident][k] = v else: storage[ident] = {k:v} #{id:{"val":v}} def get(k): ident =get_ident() return storage[ident][k] def task(arg): set("val",arg) time.sleep(1) v = get("val") # i 1,2,3,4,5... print(v) for i in range(10): t = Thread(target=task,args=(i,)) t.start()
用面向对象实现
import time from threading import Thread,get_ident class Local(object): def __init__(self): # self.storage = {} 会出现递归 走这步触发了__setattr__,然后下面self.storage为空字典,直接又走__getattr__方法 object.__setattr__(self,"storage",{}) #防止__setattr__递归,给父级object设置storage.就不会走Local里的__setattr__了 def __setattr__(self,k,v): ident = get_ident() if ident in self.storage: self.storage[ident][k] = v else: self.storage[ident] = {k:v} #{id:{"val":v}} def __getattr__(self,k): ident = get_ident() return self.storage[ident][k] obj = Local() def task(arg): obj.val = arg time.sleep(1) print(obj.val)# i 1,2,3,4,5... for i in range(10): t = Thread(target=task, args=(i,)) t.start()