并发编程:生产消费模型、死锁与Rlock、线程、守护线程、信号量、锁
本文目录:
一、队列
什么是队列
类似于链表与堆栈一样,队列也是存储数据的结构,在队列中数据进入队列的顺序很重要,一般来说,队列就是一群人或者事务安排好的顺序等待接受服务或者处理。
定义 :队列,有称之为伫列(queue),是先进先出的线性表,在具体应用中通常用链表或者数组来实现,队列只允许在后端进行插入操作,在前端(称为front)进行删除操作。
为什么需要队列
在进程彼此之间相互隔离要实现进程间的通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用信息传递的。
怎么用队列
创建队列底层就是以管道和锁定的方式实现的:
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
参数:maxsize是队列中允许最大项数,省略则无大小限制
方法介绍:
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写 入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产 生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不 会导致get()方法返回错误。
q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消 耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止 这种行为
''' multiprocessing模块支持进程间通信的两种主要形式:管道和队列 都是基于消息传递实现的,但是队列接口 ''' from multiprocessing import Process,Queue import time q=Queue(3) #put ,get ,put_nowait,get_nowait,full,empty q.put(3) q.put(3) q.put(3) print(q.full()) #满了 print(q.get()) print(q.get()) print(q.get()) print(q.empty()) #空了
二、生产者消费者模型
什么是生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题,该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列实现生产者消费者模型
为什么要用生产者消费者模型
在多线程开发当中,如果生产者处理的任务过快,消费者来不及消化,那么必须等待消费者处理完,相反如果消费者消化速度快于生产者,也要等待生产者,为了达到生产与消费之间达到平衡,引入了该模型。
生产者消费者模型实例
import time,random from multiprocessing import Process,Queue # 制作热狗 def make_hotdog(queue,name): for i in range(3): time.sleep(random.randint(1,2)) print("%s 制作了一个热狗 %s" % (name,i)) # 生产得到的数据 data = "%s生产的热狗%s" % (name,i) # 存到队列中 queue.put(data) # 装入一个特别的数据 告诉消费方 没有了 #queue.put(None) # 吃热狗 def eat_hotdog(queue,name): while True: data = queue.get() if not data:break time.sleep(random.randint(1, 2)) print("%s 吃了%s" % (name,data)) if __name__ == '__main__': #创建队列 q = Queue() p1 = Process(target=make_hotdog,args=(q,"钻钻的热狗店")) p2 = Process(target=make_hotdog, args=(q, "egon的热狗店")) p3 = Process(target=make_hotdog, args=(q, "老王的热狗店")) c1 = Process(target=eat_hotdog, args=(q,"思聪")) c2 = Process(target=eat_hotdog, args=(q, "李哲")) p1.start() p2.start() p3.start() c1.start() c2.start() # 让主进程等三家店全都做完后.... p1.join() p2.join() p3.join() # 添加结束标志 注意这种方法有几个消费者就加几个None 不太合适 不清楚将来有多少消费者 q.put(None) q.put(None) # 现在 需要知道什么时候做完热狗了 生产者不知道 消费者也不知道 # 只有队列知道 print("主进程over") # 生产方不生产了 然而消费方不知道 所以已知等待 get函数阻塞 # 三家店都放了一个空表示没热狗了 但是消费者只有两个 他们只要看见None 就认为没有了 # 于是进程也就结束了 造成一些数据没有被处理 # 等待做有店都做完热狗在放None
import time,random from multiprocessing import Process,JoinableQueue # 制作热狗 def make_hotdog(queue,name): for i in range(3): time.sleep(random.randint(1,2)) print("%s 制作了一个热狗 %s" % (name,i)) # 生产得到的数据 data = "%s生产的热狗%s" % (name,i) # 存到队列中 queue.put(data) # 装入一个特别的数据 告诉消费方 没有了 #queue.put(None) # 吃热狗 def eat_hotdog(queue,name): while True: data = queue.get() time.sleep(random.randint(1, 2)) print("%s 吃了%s" % (name,data)) # 该函数就是用来记录一共给消费方多少数据了 就是get次数 queue.task_done() if __name__ == '__main__': #创建队列 q = JoinableQueue() p1 = Process(target=make_hotdog,args=(q,"邵钻钻的热狗店")) p2 = Process(target=make_hotdog, args=(q, "egon的热狗店")) p3 = Process(target=make_hotdog, args=(q, "老王的热狗店")) c1 = Process(target=eat_hotdog, args=(q,"思聪")) c2 = Process(target=eat_hotdog, args=(q, "李哲")) p1.start() p2.start() p3.start() # 将消费者作为主进程的守护进程 c1.daemon = True c2.daemon = True c1.start() c2.start() # 让主进程等三家店全都做完后.... p1.join() p2.join() p3.join() # 如何知道生产方生产完了 并且 消费方也吃完了 # 方法一:等待做有店都做完热狗在放None # # 添加结束标志 注意这种方法有几个消费者就加几个None 不太合适 不清楚将来有多少消费者 # q.put(None) # q.put(None) # 主进程等到队列结束时再继续 那队列什么时候算结束? 生产者已经生产完了 并且消费者把数据全取完了 q.join() # 已经明确生产放一共有多少数据 # 现在 需要知道什么时候做完热狗了 生产者不知道 消费者也不知道 # 只有队列知道 print("主进程over") # 生产方不生产了 然而消费方不知道 所以一直等待 get函数阻塞 # 三家店都放了一个空表示没热狗了 但是消费者只有两个 他们只要看见None 就认为没有了 # 于是进程也就结束了 造成一些数据没有被处理
#生产者消费者模型总结 #程序中有两类角色 一类负责生产数据(生产者) 一类负责处理数据(消费者) #引入生产者消费者模型为了解决的问题是: 平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度 #如何实现: 生产者<-->队列<——>消费者 #生产者消费者模型实现类程序的解耦和
三、线程及守护线程
什么是线程
是操作系统能够进行运算调度最小的单位,它被包含在进程之中,是进程中的实际运作单位,一条线程值的是进程中的一个单一顺序的控制流,一个进程并行执行不同的任务
为什么用线程
以下有多个场景需要用到多线程:
1.避免阻塞
单个线程中的程序是按顺序执行的,如果前面的操作发生阻塞,那么久会影响到后面的操作,这时候可以采用多线程
2.避免CPU空转
举例当web server为例,来了一个https请求,服务器就会处理一个请求,依次处理后续请求,CPU会有大量闲置时间,当然https请求来了,会涉及到RPC、数据库的访问、磁盘IO等操作,这些都会拖慢CPU,而在等待的时候CPU就一直等在哪里,影响到了server的性能。因此可以采用多线程时遇到IO操作,cpu就会迅速切换到其他程序处理任务,大大提高了CPU的性能。
3.提升性能
打个比方一个餐厅只有一个厨师一口灶头,来个10个不同的菜,按程序只能做十次才能结束任务,现在老板找来9个厨师,添加了9个灶台,完成10个菜所用的时间就比之前提高了90%了。
怎么用线程
from threading import Thread import time def task(): time.sleep(5) print("子线程...") t = Thread(target=task) t.daemon = True # 守护线程 执行顺序与进程中一样 t.start() print("over")
from threading import Thread # a = 100 # # def task(): # global a # a = 1 # print("这是给子线程执行的任务!") # # # 创建一个子线程 # t = Thread(target=task) # # 启动这个子线程 # t.start() # print("主") # print(a) # 在多进程中 开启子进程需要消耗大量的资源 所以主进程会先比子进程执行 # 子线程的开启速度比进程快的多 # 在多线程中 子线程可以直接访问主线程的内容 # 多个线程之间时平等的 所以不存在父子关系 # 在今后的开发中 每当出现i/o阻塞 比较耗时的操作 import time,os def task(): time.sleep(2) print("子线程 run.....") print(os.getpid()) t = Thread(target=task) t.start() # 主线程等到子线程结束 t.join() print("over") print(os.getpid())
线程和进程的区别
进程是一个资源单位
一个进程可以包含多个线程
多个线程之间的数据可以共享
线程开销比进程小
在多个线程中CPU切换速度会比非常快,但资源消耗没有进程高
四、线程常用方法
from threading import Thread,current_thread,active_count,enumerate import time def task(): print("子线程...") time.sleep(1) # 获取当前线程对象 非常常用 print(current_thread()) t = Thread(target=task,name="矮根线程!") # t.daemon = True # 守护线程 执行顺序与进程中一样 print(t.name) print(t) t.start() # 获取当前活跃线程的数量 print(active_count()) # 返回活跃的线程对象枚举 print(enumerate()) print("over")
五、启动线程的另一种方式
from threading import Thread,current_thread
# 写一个类继承Thread class MyThread(Thread): def run(self): print("run 函数执行!") print(current_thread()) mt = MyThread() mt.start() print(current_thread())
六、锁
加入锁相当于给所有的线程串联起来,先拿到锁的线程执行完释放锁资源、依次类推
from threading import Thread,Lock # 创建一个互斥锁 mutex = Lock() def task1(): # 锁定 mutex.acquire() for i in range(100): print("===================") # 打开 mutex.release() def task2(): mutex.acquire() for i in range(100): print("!!!!!!!!!!!!!!!!!!") mutex.release() def task3(): mutex.acquire() for i in range(100): print("********************") mutex.release() t1 = Thread(target=task1) t2 = Thread(target=task2) t3 = Thread(target=task3) t1.start() t2.start() t3.start()
七、锁死
from threading import Thread,Lock import time,random mutex1 = Lock() mutex2 = Lock() def fun1(): mutex1.acquire() print("func1抢到了锁1") time.sleep(1) mutex2.acquire() print("func1抢到了锁2") mutex2.release() print("func1释放了锁2") mutex1.release() print("func1释放了锁1") def fun2(): mutex2.acquire() print("func2抢到了锁2") time.sleep(1) mutex1.acquire() print("func2抢到了锁1") mutex1.release() print("func2释放了锁2") mutex2.release() print("func2释放了锁1") # def fun3(): # fun1() # fun2( t1 = Thread(target=fun1) t1.start() t2 = Thread(target=fun2) t2.start()
# 以下控制台内容
D:\Python36\python.exe D:/Myproject/work_log/test2.py
func1抢到了锁1
func2抢到了锁2
# 当func1抢到锁1后,准备抢锁2,可以被func2抢到锁2,这个时候func1在等func2释放锁2的资源,反之func2在等func1释放锁1的资源,但情况是他们都在互相等对方的释放锁,却没
有一个释放,这是就出现程序卡在那里,发生锁死的情况
八、死锁
from threading import Thread,Lock,current_thread,RLock import time # 叉子 locka = RLock() # 盘子 lockb = RLock() def task1(): print(current_thread()) locka.acquire() print("抢到叉子 需要盘子") time.sleep(0.1) lockb.acquire() print("吃饭") lockb.release() locka.release() def task2(): print(current_thread()) lockb.acquire() print("抢到盘子 需要叉子") time.sleep(0.1) locka.acquire() print("吃饭") locka.release() lockb.release() t1 = Thread(target=task1) t1.start() t2 = Thread(target=task2) t2.start() # 死锁发生的条件 有多个线程 多个锁 如果只有一个锁 无论是LOCK RLOK 卡不死(前提是逻辑上没有错误) # RLock 就算你的代码逻辑不对 同一个线程多次对一个锁执行acquire 也不会卡死
九、单个锁能不能死锁
from threading import Thread,Lock,RLock,current_thread l = Lock() # 互斥锁 # l.acquire() # print("zxxzxxxzxzx") # l.acquire() # print("aaaaaaaaa") # RLock 递归锁 重入锁 可以多次执行acquire # lock = RLock() # # lock.acquire() # print("aaaaaaaaaaa") # lock.acquire() # print("bbbbbbbbbbb") import time lock = RLock() # 对于同一个线程而言 可以多次acquire 其他线程会被阻塞 def task(): lock.acquire() for i in range(5): time.sleep(1) print(current_thread()) lock.release() Thread(target=task).start() Thread(target=task).start() #
十、信号旗
from threading import Thread,Semaphore,current_thread,active_count import time # 用于控制 同时执行被锁定代码的线程数量 也就是线程的并发数量 # 也是一种锁 sm = Semaphore(1) def task(): sm.acquire() for i in range(10): print(current_thread()) time.sleep(0.5) sm.release() def task2(): for i in range(10): print(current_thread()) time.sleep(0.5) for i in range(5): Thread(target=task).start() Thread(target=task2).start() print(active_count())
资料参考:
https://blog.csdn.net/Pandaminn/article/details/92682237
https://www.cnblogs.com/linhaifeng/articles/7428877.html#_label1
https://blog.csdn.net/CYTDCHE/article/details/79075013