我们每一个请求进来的时候都开一个进程肯定不合理,那么如果每一个请求进来都是串行的,那么根本实现不了并发,所以我们假定每一个请求进来使用的是线程。
那么线程中数据互相不隔离,存在修改数据的时候数据不安全的问题。
假定我们的需求是,每个线程都要设置值,并且该线程打印该线程修改的值。
| 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抢票的应用场景,而我们是要做请求对象的并发,想要实现的是该线程对于请求对象这部分内容有任何修改并不影响其他线程。所以使用方案二
多个线程修改同一个数据,复制多份数据给每个线程用,为每个线程开辟一块空间进行数据存储
实例:
| 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实例化的对象,实现了多线程修改同一个数据,每个线程都复制了一份数据,并且修改的也都是自己的数据。达到了我们想要的效果。
实例:
| from threading import get_ident,Thread,current_thread |
| |
| import time |
| |
| class Local(object): |
| storage = {} |
| 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): |
| |
| 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} |
| } |
运行效果:
实例: