- 一、进程的概念
假如有两个程序A和B,首先执行A程序的时候遇到数据阻塞(A程序在等待数据的输入),那么这个时候CPU就是空闲的,我们为了充分利用资源能不能先将A程序撂一边,先执行B程序,等到A程序接收到数据再将B程序暂停继续执行A程序呢?答案显然是可以的,这个时候有一个关键词:切换。也就是这就是CPU从A切换到B,再从B切换到A。
那么既然是切换,那么就涉及到状态的保存,状态的恢复,以及程序A与程序B所占用的资源(数据,CPU,键盘等等),自然而然就需要有一个东西可以去记录A程序和B程序的资源,以及如何去识别A和B,这个时候就有了一个进程的抽象。
进程定义
进程就是一个程序在一个数据集运行的一次动态执行过程
进程一般由 程序、数据集和进程控制块 三部分组成
我们编写的程序用来描述进程要完成的功能以及如何完成;
数据集则是程序在执行过程中用到的资源;
进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
- 二、线程的概念
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷, 使到进程内并发成为可能。
假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有 一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多 个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的 任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且 有共同都需要拥有的东西-------文本内容,不停的切换造成性能上的损失。若有一种机制,可以使 任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带 来的性能损耗,那就好了。是的,这种机制就是线程。
线程定义
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序 计数器、寄存器集合和堆栈共同组成。
线程的引入减小了程序并发执行时的开销,提高了操作系统的并发 性能。线程没有自己的系统资源。
- 三、进程和线程的关系区别
1 一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)
2 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
3 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和 程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
4 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调 度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
5.进程是最小的资源单位,线程是最小的执行单位
- 四、为何要开多线程
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点:
1. 多线程共享一个进程的地址空间
2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用
3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
- 五、线程
(1)线程的两种调用方式
直接调用
1 import threading,time 2 def hi(num): 3 start_time = time.time() 4 print('say hi %d'%num) 5 time.sleep(3) 6 stop_time = time.time() 7 print('总共用的时间是%s'%(stop_time-start_time)) 8 9 if __name__ == '__main__': 10 t1 = threading.Thread(target=hi,args=(10,)) 11 t1.start() 12 t2 = threading.Thread(target=hi, args=(9,)) 13 t2.start() 14 # say hi 10 15 # say hi 9 16 # 总共用的时间是3.000171422958374 17 # 总共用的时间是3.001171588897705
自定义threading类调用
1 import threading,time 2 class Mythreading(threading.Thread): 3 def __init__(self,num): 4 threading.Thread.__init__(self) 5 self.num = num 6 def run(self): ## 必须要重写run()方法 7 start_time = time.time() 8 print('say hi %d'%self.num) 9 time.sleep(3) 10 stop_time = time.time() 11 print('总共用的时间是%s'%(stop_time-start_time)) 12 13 if __name__ == '__main__': 14 t = Mythreading(10) 15 t.start() 16 time.sleep(2) 17 print('ending....') 18 # say hi 10 19 # ending.... 20 # 总共用的时间是3.000171422958374
(2)join方法
join方法:
1.join方法的作用是阻塞主进程(挡住,无法执行join以后的语句),专注执行多线程。
2.多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。
3.无参数,则等待到该线程结束,才开始执行下一个线程的join。
1 import threading,time 2 def music(): 3 print('start to music %s'%time.ctime()) 4 time.sleep(3) 5 print('stop to music %s'%time.ctime()) 6 7 def game(): 8 print('start to play game %s'%time.ctime()) 9 time.sleep(5) 10 print('stop to play game %s'%time.ctime()) 11 12 if __name__ == '__main__': 13 t1 = threading.Thread(target=music) 14 t1.start() 15 t2 = threading.Thread(target=music) 16 t2.start() 17 t1.join() ## 这里加了join方法,也就是说t1没有执行完后面的代码就不走了 18 print('ending.....') 19 # start to music Fri Apr 10 23:50:07 2020 20 # start to music Fri Apr 10 23:50:07 2020 21 # stop to music Fri Apr 10 23:50:10 2020 22 # ending..... 23 # stop to music Fri Apr 10 23:50:10 2020
(3)setDaemon方法
setDaemon(True):
setDaemon一定要在start方法之前加,设置为守护线程。结果和join方法相反
主线程执行完要看子线程执行结束情况,如果子线程还没结束,那么主线程就需要等待子线程结束再一起退出。这时子线程1如果设置成守护线程,那么主线程执行结束就直接无视子线程1了,不需要等子线程1了。
1 import threading,time 2 def music(): 3 print('start to music %s'%time.ctime()) 4 time.sleep(3) 5 print('stop to music %s'%time.ctime()) 6 7 def game(): 8 print('start to play game %s'%time.ctime()) 9 time.sleep(5) 10 print('stop to play game %s'%time.ctime()) 11 12 if __name__ == '__main__': 13 t1 = threading.Thread(target=music) 14 t2 = threading.Thread(target=game) 15 t2.setDaemon(True) ## setDaemon 必须在start()前设置 16 t1.start() 17 t2.start() 18 print('ending....',time.ctime()) 19 # start to music Sat Apr 11 10:23:22 2020 20 # start to play game Sat Apr 11 10:23:22 2020 21 # ending.... Sat Apr 11 10:23:22 2020 22 # stop to music Sat Apr 11 10:23:25 2020
(4)其它方法
1 # run(): 线程被cpu调度后自动执行线程对象的run方法 2 # start():启动线程活动。 3 # isAlive(): 返回线程是否活动的。 4 # getName(): 返回线程名。 5 # setName(): 设置线程名。 6 7 threading模块提供的一些方法: 8 # threading.currentThread(): 返回当前的线程变量。 9 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 10 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
- 五、并行&并发 同步&异步
并发&并行
并行:系统具有同时处理多个任务(动作)的能力,通俗点就是多核CPU各自同一时刻运行着不同的应用。
并发:系统具有处理多个任务(动作)的能力,通俗点来说就是单核CPU可以做到来回切换处理QQ音乐、暴风影音、微信等应用的能力,但是要注意一点,这不是同一时刻单核CPU可以处理多个应用,而是因为切换速度很快,让你感知不到这个切换。
并发和并行的关系:并行是并发的子集
同步&异步
同步:执行过程中遇到IO阻塞(等待外部数据)时,选择------------等:不继续接下来的操作,直到等到了数据再继续接下来的操作---例如:打电话
异步: ----------不等:继续其它操作,直到数据到了在继续刚刚的操作---例如:发短信
- 六、GIL(全局解释器锁)
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许同一个进程的一个线程运行
假设我们需要完成两个功能,累加和类乘,现在分别用串行和多线程来计算运行所用的时间
1 import threading,time 2 start = time.time() 3 def add(): 4 num = 0 5 for i in range(100000000): 6 num += i 7 print('add运行结束了') 8 9 def mul(): 10 num = 1 11 for i in range(1,100000): 12 num *= i 13 print('mul运行结束了') 14 15 add() 16 mul() 17 print('ending。。。') 18 print('消耗的时间是:',(time.time()-start)) 19 # add运行结束了 20 # mul运行结束了 21 # ending。。。 22 # 消耗的时间是: 13.369764566421509
1 import threading,time 2 start = time.time() 3 def add(): 4 num = 0 5 for i in range(100000000): 6 num += i 7 print('add运行结束了') 8 9 def mul(): 10 num = 1 11 for i in range(1,100000): 12 num *= i 13 print('mul运行结束了') 14 15 t1 = threading.Thread(target=add) 16 t2 = threading.Thread(target=mul) 17 t1.start() 18 t2.start() 19 t1.join() 20 t2.join() 21 print('ending。。。') 22 print('消耗的时间是:',(time.time()-start)) 23 # mul运行结束了 24 # add运行结束了 25 # ending。。。 26 # 消耗的时间是: 13.208755731582642
可以看到,串行用到的时间是(13.369764566421509),多线程所用的时间是(13.208755731582642)。这次用到的Python解释器是3.7,在这里已经优化很多了。如果这是在2.7中运行的话,多线程运行的时间要多于串行所用的时间,这是为什么?
这是因为Python解释器中加了一把GIL锁,正因为有这把锁,同一时刻同一个进程中只允许有一个线程在运行,那么也就是在计算中不能用两个CPU来处理两个函数
我们可以看到上面的过程,add函数和mul函数在争夺锁出去运行,可能先是add拿到权限了,出去运行了一会儿时间轮循(也就是过一段时间就要退出CPU)进行切换,add回去了,锁被mul拿到了,运行了一会儿又回来了,add又出去继续运行。。。这样一个过程需要大量的切换。发过来我们看,如果是串行的话,运行完add自然就轮到mul运行了,这中间没有切换的过程,所以效率肯定要比多线程的要快。
总结:
1、任务分为:IO密集型 和计算密集型
2、对于IO密集型:Python的 多线程 是有效的,也可以用进程+协程
对于计算密集型:Python就不适用了,Python没有比较有效的方法适用计算密集型的任务。
- 七、同步锁
我们现在有一个需求:用一百个线程来做一个累减的操作
1 import threading,time 2 num = 100 3 def num_reduce(): 4 global num 5 temp = num 6 time.sleep(0.001) 7 num = temp-1 8 9 t_list = [] 10 for i in range(100): 11 t = threading.Thread(target=num_reduce) 12 t.start() 13 t_list.append(t) 14 for j in t_list: 15 j.join() 16 17 print(num) ## 79 78 76 79 76
可以看到,结果并不唯一,为什么结果不是0?
这是因为,假如有一百个人来代表一百个线程,首先第一个人易烊千玺拿到num=100,然后进入IO阻塞,释放出锁,这时第二个人胡歌拿到锁了,拿到的num仍然是100,进入IO阻塞,释放锁,第三个人蔡徐坤拿到锁,拿到的num仍然是100.。。。可能到第八个人的时候易烊千玺time.sleep结束了,num变为99,这时第八个人王俊凯进来拿到的num就是99了。。。这个过程下去
我们来看一张图
归根到底,就是因为线程共用数据资源,那我们把资源共用的那一部分锁住不就行了吗,这个时候就用到同步锁了
1 import threading,time 2 num = 100 3 lock = threading.Lock() ## 创建一个同步锁对象 4 def num_reduce(): 5 global num 6 lock.acquire() ## 上锁 7 temp = num 8 time.sleep(0.001) 9 num = temp-1 10 lock.release() ## 放锁 11 12 t_list = [] 13 for i in range(100): 14 t = threading.Thread(target=num_reduce) 15 t.start() 16 t_list.append(t) 17 for j in t_list: 18 j.join() 19 20 print(num) ## 0
- 八、死锁和递归锁
有两个人犬痣聋和蔡徐坤,犬痣聋手里拿着一个篮球,蔡徐坤手里拿着一个足球,蔡徐坤想要犬痣聋手里的篮球,而犬痣聋想要蔡徐坤手里的足球,谁也不愿意先给对方,两人就这样僵持着。
其实线程也会出现这种情况,就是死锁
1 import threading,time 2 3 Lock_A = threading.Lock() 4 Lock_B = threading.Lock() 5 6 class Mythread(threading.Thread): 7 def sectionA(self): 8 Lock_A.acquire() 9 print('%s拿到Lock_A'%self.name) 10 time.sleep(2) 11 12 Lock_B.acquire() 13 print('%s拿到Lock_B'%self.name) 14 time.sleep(1) 15 16 Lock_B.release() 17 Lock_A.release() 18 19 def sectionB(self): 20 Lock_B.acquire() 21 print('%s拿到Lock_B' % self.name) 22 time.sleep(2) 23 24 Lock_A.acquire() 25 print('%s拿到Lock_A' % self.name) 26 time.sleep(1) 27 28 Lock_A.release() 29 Lock_B.release() 30 31 def run(self): 32 self.sectionA() 33 self.sectionB() 34 35 if __name__ == '__main__': 36 t_list = [] 37 for i in range(5): 38 t = Mythread() 39 t.start() 40 t_list.append(t) 41 for j in t_list: 42 j.join() 43 44 print('ending。。。') 45 46 # Thread-1拿到Lock_A 47 # Thread-1拿到Lock_B 48 # Thread-1拿到Lock_B 49 # Thread-2拿到Lock_A
造成死锁的原因是线程2拿到了A锁,在等着拿B锁,而线程1拿到了B锁,在等着拿A锁,两个线程谁也不让着谁,那就只能干耗着
那我们很容易就想到解决死锁的办法:我把A锁和B锁绑定在一起,拿到A锁就只有我能拿B锁,其他人都不能拿,这不就解决了吗?
这就是递归锁(RLock)
1 import threading,time 2 3 l_Lock = threading.RLock() ## 递归锁对象 4 5 class Mythread(threading.Thread): 6 def sectionA(self): 7 l_Lock.acquire() ## account = 1 8 print('%s拿到Lock_A'%self.name) 9 time.sleep(2) 10 11 l_Lock.acquire() ## account = 2 12 print('%s拿到Lock_B'%self.name) 13 time.sleep(1) 14 15 l_Lock.release() ## account = 1 16 l_Lock.release() ## account = 0 17 18 def sectionB(self): 19 l_Lock.acquire() 20 print('%s拿到Lock_B' % self.name) 21 time.sleep(2) 22 23 l_Lock.acquire() 24 print('%s拿到Lock_A' % self.name) 25 time.sleep(1) 26 27 l_Lock.release() 28 l_Lock.release() 29 30 def run(self): 31 self.sectionA() 32 self.sectionB() 33 34 if __name__ == '__main__': 35 t_list = [] 36 for i in range(5): 37 t = Mythread() 38 t.start() 39 t_list.append(t) 40 for j in t_list: 41 j.join() 42 43 print('ending。。。') 44 # Thread-1拿到Lock_A 45 # Thread-1拿到Lock_B 46 # Thread-1拿到Lock_B 47 # Thread-1拿到Lock_A 48 # Thread-3拿到Lock_A 49 # Thread-3拿到Lock_B 50 # Thread-3拿到Lock_B 51 # Thread-3拿到Lock_A 52 # Thread-5拿到Lock_A 53 # Thread-5拿到Lock_B 54 # Thread-2拿到Lock_A 55 # Thread-2拿到Lock_B 56 # Thread-4拿到Lock_A 57 # Thread-4拿到Lock_B 58 # Thread-4拿到Lock_B 59 # Thread-4拿到Lock_A 60 # Thread-2拿到Lock_B 61 # Thread-2拿到Lock_A 62 # Thread-5拿到Lock_B 63 # Thread-5拿到Lock_A 64 # ending。。。
递归锁实现流程:
Thread1拿到递归锁(RLock),这时递归锁的计时器标识account=1,其它人就只能等着,只有account=0时其他人才能抢锁。
- 九、同步对象(event)
事件是一个简单的同步对象;事件表示内部标志,线程可以等待标志被设置,或者自己设置或清除标志。
创建一个同步对象:event = threading.Event()
客户端线程可以等待标志被设置
event.wait()
服务器线程可以设置或重置它
event.set()
event.clear()
如果设置了标志,wait方法不会做任何事情。如果标志被清除,wait将阻塞,直到它再次被设置。任何数量的线程都可能等待相同的事件。
通俗点就是如果一个线程A设置为同步对象,另外几个线程有wait()方法,那么这些线程就得等待线程A
1 import threading,time 2 class Boss(threading.Thread): 3 def run(self): 4 print("BOSS:今晚大家都要加班到22:00。") 5 print(event.isSet()) ## False,wait后面不能走 6 event.set() ## 设置为True,wait 可以走了 7 time.sleep(5) 8 print("BOSS:<22:00>可以下班了。") 9 print(event.isSet()) ## False 10 event.set() ## 设置为True,wait后面可以走了 11 class Worker(threading.Thread): 12 def run(self): 13 event.wait() 14 print("Worker:哎……命苦啊!") 15 time.sleep(1) 16 event.clear() ## 清除标识,也就是改为False,wait不能走了 17 event.wait() 18 print("Worker:OhYeah!") 19 if __name__=="__main__": 20 event=threading.Event() ## 同步对象 21 threads=[] 22 for i in range(5): 23 threads.append(Worker()) 24 threads.append(Boss()) 25 for t in threads: 26 t.start() 27 for t in threads: 28 t.join()
- 十、同步信号量(Semaphore)
同步信号量可以控制同时可以有几个线程去执行,本质上也是一把锁
1 import threading,time 2 class Mythread(threading.Thread): 3 def run(self): 4 Semaphore.acquire() 5 print(self.name) 6 time.sleep(3) 7 Semaphore.release() 8 9 if __name__ == '__main__': 10 t_list = [] 11 Semaphore = threading.Semaphore(5) ## 同时允许五个线程 12 for i in range(100): 13 t = Mythread() 14 t.start() 15 t_list.append(t) 16 for j in t_list: 17 j.join() 18 print('ending...')
- 十一、线程队列------多线程利器
我们来看一下两个线程来删除一个列表的值时会出现什么样的情况
1 import threading,time 2 3 li=[1,2,3,4,5] 4 5 def pri(): 6 while li: 7 a=li[-1] 8 print(a) 9 time.sleep(1) 10 try: 11 li.remove(a) 12 except Exception as e: 13 print('----',a,e) 14 15 t1=threading.Thread(target=pri,args=()) 16 t1.start() 17 t2=threading.Thread(target=pri,args=()) 18 t2.start() 19 # 5 20 # 5 21 # 4 22 # ---- 5 list.remove(x): x not in list 23 # 4 24 # 3 25 # ---- 4 list.remove(x): x not in list 26 # 3 27 # 2 28 # ---- 3 list.remove(x): x not in list 29 # 2 30 # 1 31 # ---- 2 list.remove(x): x not in list 32 # 1 33 # ---- 1 list.remove(x): x not in list
可以看得出来,列表是不安全的数据结构,当两个线程同时共享同一个列表时,列表可能会出错
所以在线程中,用队列相比列表安全得多了
1、队列
队列也是一种数据结构,效果和列表一样,只适用于线程和进程中
queue队列类的方法
1 创建一个“队列”对象 2 import Queue 3 q = Queue.Queue(maxsize = 10) 4 Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。 5 6 将一个值放入队列中 7 q.put(10) 8 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为 9 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。 10 11 将一个值从队列中取出 12 q.get() 13 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True, 14 get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。 15 16 Python Queue模块有三种队列及构造函数: 17 1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize) 18 2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize) 19 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 20 21 此包中的常用方法(q = Queue.Queue()): 22 q.qsize() 返回队列的大小 23 q.empty() 如果队列为空,返回True,反之False 24 q.full() 如果队列满了,返回True,反之False 25 q.full 与 maxsize 大小对应 26 q.get([block[, timeout]]) 获取队列,timeout等待时间 27 q.get_nowait() 相当q.get(False) 28 非阻塞 q.put(item) 写入队列,timeout等待时间 29 q.put_nowait(item) 相当q.put(item, False) 30 q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 31 q.join() 实际上意味着等到队列为空,再执行别的操作
2、三种队列模式:
(1)先进先出FIFO(first in first out)(默认)
1 import queue 2 q = queue.Queue(3) ## 创建一个线程队列,参数3代表最多能放3个队列 3 q.put(21) 4 q.put('hello') 5 q.put({'name':'alex'}) 6 # q.put((12,23),False) ## put(self, item, block=True, timeout=None) 这里block默认值为True,如果满了则不能塞数据,必须要有数据被取了才能往里塞,队列会卡在这里。如果block设置为False,则满了再塞就报错 7 while True: 8 data = q.get() ## get(self, block=True, timeout=None) 这里block默认值为True,如果空了就等待,直到有数据被塞入,如果设置为False则空了就报错 9 print(data) 10 # 21 11 # hello 12 # {'name': 'alex'}
(2)先进后出LIFO(later in first out)
1 import queue 2 q = queue.LifoQueue(3) 3 4 q.put(123) 5 q.put('hello') 6 q.put({'name':'alex'}) 7 8 while 1: 9 date = q.get(block=False) 10 print(date) 11 print('------------------') 12 # {'name': 'alex'} 13 # ------------------ 14 # hello 15 # ------------------ 16 # 123 17 # ------------------
(3)优先级
1 import queue 2 q = queue.PriorityQueue() 3 q.put([2,23]) ## 第一个元素是优先级,数字越小优先级越高 4 q.put([4,'hello']) 5 q.put([3,{'name':'alex'}]) 6 while True: 7 data = q.get() 8 print(data) 9 # [2, 23] 10 # [3, {'name': 'alex'}] 11 # [4, 'hello']
3、生产者消费者模型
为什么要使用生产者消费者模型?
在线程世界中,生产者就是生产数据的线程,消费者就是消费数据的线程。如果生产者生产数据很快,消费者消费数据很慢,那么生产者就需要等待消费者处理完,才能继续产生数据;反之也一样,消费者就必须要等待生产者生产出数据,才能继续处理。
什么是生产者消费者模型?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。而队列就是两者之间的容器,也就是中介。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。
1 import threading,queue,random,time 2 3 q = queue.Queue() 4 5 def Produce(name): 6 count = 1 7 while count < 4: 8 print('开始制作包子...') 9 time.sleep(random.randrange(3)) 10 q.put(count) 11 print('Produce %s 制作好了第%s个包子...'%(name,count)) 12 count += 1 13 print('ok') 14 15 def Consumer(name): 16 count = 1 17 while count<4: 18 time.sleep(random.randrange(4)) 19 if not q.empty(): 20 date = q.get() 21 print(date) 22 print('