(一)生产与消费者模式
- 什么是⽣产者消费者模式:
⽣产者消费者模式是通过⼀个容器来解决⽣产者和消费者的强耦合问题。
⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产 者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取,阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒。
这个阻塞队列就是⽤来给⽣产者和消费者解耦的,纵观⼤多数设计模式,都会找⼀个第三者出来进⾏解耦。
- 为什么要使⽤⽣产者和消费者模式:
在线程世界⾥,⽣产者就是⽣产数据的线程,消费者就是消费数据的线程。
在多线程开发当中,如果⽣产者处理速度很快,⽽消费者处理速度很慢,那 么⽣产者就必须等待消费者处理完,才能继续⽣产数据。同样的道理,如果 消费者的处理能⼒⼤于⽣产者,那么消费者就必须等待⽣产者。
为了解决这 个问题于是引⼊了⽣产者和消费者模式。
实例演示:
1 import threading 2 from queue import Queue 3 import time 4 5 6 class Producer(threading.Thread): 7 def run(self): 8 count = 0 9 while True: 10 #如果缓存队列中小于1000个元素,就生产100个,否则就休息 11 if queue.qsize() <= 1000: 12 for i in range(100): 13 count = count + 1 14 msg = '生成产品' + str(count) 15 print(msg) 16 queue.put(msg) 17 time.sleep(0.5) 18 19 20 class Consumer(threading.Thread): 21 def run(self): 22 while True: 23 #如果缓存队列中大于100个元素,就消耗50个,否则休息 24 if queue.qsize() >= 100: 25 for i in range(50): 26 msg = self.name + '消耗' + queue.get() 27 print(msg) 28 time.sleep(1) 29 30 31 32 if __name__ == "__main__": 33 #建立一个缓冲队列 34 queue = Queue() 35 36 #初始化队列 37 for i in range(100): 38 msg = '初始产品' + str(i) 39 queue.put(msg) 40 41 #定义两个生产者 42 for i in range(2): 43 p = Producer() 44 p.start() 45 46 #定义五个消费者 47 for i in range(5): 48 c = Consumer() 49 c.start() 50 51 52 》》》输出: 53 生成产品1 54 生成产品2 55 生成产品3 56 生成产品4 57 生成产品5生成产品1 58 生成产品6 59 60 生成产品2 61 生成产品3生成产品7 62 生成产品8 63 生成产品4 64 。。。。。
对于Queue的说明:
1. 对于Queue,在多线程通信之间扮演重要的⻆⾊
2. 添加数据到队列中,使⽤put()⽅法
3. 从队列中取数据,使⽤get()⽅法
4. 判断队列中是否还有数据,使⽤qsize()⽅法
(二)ThreadLocal
在多线程环境下,每个线程都有⾃⼰的数据。
⼀个线程使⽤⾃⼰的局部变量⽐使⽤全局变量好,因为局部变量只有线程⾃⼰能看⻅,不会影响其他线程,⽽全局变量的修改必须加锁。
如果要在一个函数中将该函数的局部变量传给另外一个函数,我们可以怎么办呢?可以采用全局字典:
- 全局字典:
⽤⼀个全局dict存放所有的Student对象,然后以thread⾃身作为key获得线程对应的Student对象
1 import threading 2 3 global_dic = {} 4 5 6 def work(name): 7 std = Student(name) 8 #把std放在全局字典里面,这样即使不同线程同时运行,也不会覆盖掉全局变量的修改 9 global_dic[threading.current_thread()] = std 10 f1() 11 f2() 12 13 14 def f1(): 15 #不传入std,直接从全局字典中访问即可 16 std = global_dic[threading.current_thread()] 17 ... 18 19 20 def f2(): 21 #任何函数都可以直接查看到当前进程的std 22 std = global_dic[threading.current_thread()] 23 ...
这种⽅式理论上是可⾏的,它最⼤的优点是消除了std对象在每层函数中的传递问题,但是每个函数获取std的代码有点low。 有没有更简单的⽅式?
- 使用ThreadLocal
1 import threading 2 3 #创建全局的TL对象 4 local_school = threading.local() 5 6 7 def process_student(): 8 #获取当前线程关联的student 9 std = local_school.student 10 print('%s (in %s)'%(std, threading.current_thread().name)) 11 12 13 def process_thread(name): 14 #绑定ThreadLocal的student: 15 local_school.student = name 16 process_student() 17 18 19 if __name__ == "__main__": 20 t1 = threading.Thread(target=process_thread, args=('LaoWang',), name='T-1') 21 t2 = threading.Thread(target=process_thread, args=('XiaoWang',), name='T-2') 22 t1.start() 23 t2.start() 24 t1.join() 25 t2.join() 26 27 28 》》》输出: 29 LaoWang (in T-1) 30 XiaoWang (in T-2)
全局变量local_school就是⼀个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。
你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写⽽互不⼲扰,也不⽤管理锁的问题,ThreadLocal内部会处理。
可以理解为全局变量local_school是⼀个dict,不但可以⽤ local_school.student,还可以绑定其他变量,如local_school.teacher等等。
ThreadLocal最常⽤的地⽅就是为每个线程绑定⼀个数据库连接,HTTP请求,⽤户身份信息等,这样⼀个线程的所有调⽤到的处理函数都可以⾮常⽅便地访问这些资源。
(三)异步
我们再来看看同步和异步:
- 同步调⽤就是你 喊 你朋友吃饭 ,你朋友在忙 ,你就⼀直在那等,等你朋友忙完了 ,你们⼀起去
- 异步调⽤就是你 喊 你朋友吃饭 ,你朋友说知道了 ,待会忙完去找你 , 你就去做别的了,等你朋友忙完,你立刻停下手中的工作一起去吃饭。
接下来,我们用一段代码来说明异步的思想:
1 from multiprocessing import Pool 2 import time 3 import os 4 5 6 def test(): 7 print('---进程池中的进程--pid=%d,ppid=%d'%(os.getpid(),os.getppid())) 8 for i in range(3): 9 print('--%d--'%i) 10 time.sleep(1) 11 return 'HAHA'#返回的值将被传入回调函数 12 13 14 def test2(args): 15 print('---callback func --pid=%d'%os.getpid()) 16 print('---callback func --args=%s'%args) 17 18 19 if __name__ == '__main__': 20 pool = Pool(3) 21 pool.apply_async(func=test, callback=test2) 22 23 while True: 24 time.sleep(1) 25 print("---主进程-pid=%d---"%os.getpid()) 26 27 28 》》》输出: 29 ---进程池中的进程--pid=13368,ppid=18320 30 --0-- 31 ---主进程-pid=18320--- 32 --1-- 33 ---主进程-pid=18320--- 34 --2-- 35 ---主进程-pid=18320--- 36 ---callback func --pid=18320 37 ---callback func --args=HAHA 38 ---主进程-pid=18320--- 39 ---主进程-pid=18320--- 40 ---主进程-pid=18320--- 41 ---主进程-pid=18320--- 42 ---主进程-pid=18320---