Python笔记_30_同步异步_锁_信号量_事件_队列_生产者与消费者模型_JoinableQueue

本文深入探讨了并发编程的核心概念,包括同步与异步的区别、阻塞与非阻塞的特性,以及锁、信号量、事件和队列在多任务处理中的应用。通过实例讲解了抢票软件的工作原理,以及如何使用锁、信号量和事件来控制并发执行,优化生产者与消费者模型,提高程序效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

同步 异步 / 阻塞 非阻塞

场景在多任务当中

  • 同步:必须等我这件事干完了,你在干,只有一条主线,就是同步
  • 异步:没等我这件事情干完,你就在干了,有两条主线,就是异步
  • 阻塞:比如代码有了input,就是阻塞,必须要输入一个字符串,否则代码不往下执行
  • 非阻塞:没有任何等待,正常代码往下执行.

  • 同步阻塞 :效率低,cpu利用不充分
  • 异步阻塞 :比如socketserver,可以同时连接多个,但是彼此都有recv
  • 同步非阻塞:没有类似input的代码,从上到下执行.默认的正常情况代码
  • 异步非阻塞:效率是最高的,cpu过度充分,过度发热

锁 lock

  • 互斥锁Lock : 互斥锁就是进程的互相排斥,
    谁先抢到资源,谁就上锁改资源内容,为了保证数据的同步性
    注意:多个锁一起上,不开锁,会造成死锁.上锁和解锁是一对.
from multiprocessing import Process,Lock
import time

# 创建一把锁
lock = Lock()
# 上锁
lock.acquire()
print("111")
# 解锁
lock.release()


# 死锁 : 只上锁,不解锁,会阻塞,产生死锁
lock.acquire()
# lock.release()
lock.acquire()
# lock.release()
lock.acquire()
# lock.release()
print("222")

比如12306抢票软件,实际上是吧mysql数据库中的内容读出来先与存在redis 的内存中,
内存的读写的速度极快,不需要再刻意的加延迟等待,即可把该有的数据更新或者返回.
等待抢票的高峰期过了,在慢慢把内存中的数据更新回MySQL当中.

模拟抢票软件
import json

# 读取票数,更新票数
def wr_info(sign,dic=None):
	if sign == "r":
		with open("ticket",mode="r",encoding="utf-8") as fp:
			dic = json.load(fp)
		return dic
	elif sign == "w":
		with open("ticket",mode="w",encoding="utf-8") as fp:
			json.dump(dic,fp)
			
# 抢票方法
def get_ticket(person):
	# 读取数据库的实际票数
	dic = wr_info("r")
	# with open("ticket",mode="r",encoding="utf-8") as fp:
		# dic = json.load(fp)
	time.sleep(0.1)
	if dic["count"] > 0:
		print("%s抢到票了" % (person) )
		dic["count"] -= 1
		# 更新数据库
		wr_info("w",dic)
		# with open("ticket",mode="w",encoding="utf-8") as fp:
			# json.dump(dic,fp)
	else:
		print("%s没有买到这个票" % (person))

# 用ticket来进行函数的统一调用
def ticket(person,lock):
	# 查询票数
	dic = wr_info("r")
	# with open("ticket",mode="r",encoding="utf-8") as fp:
		# dic = json.load(fp)
	print("%s 查询余票 : %s" % (person,dic["count"]))
	lock.acquire()
	
	# 开始抢票
	get_ticket(person)
	lock.release()

if __name__ == "__main__":
	lock = Lock()
	for i in range(10):
		p = Process(target=ticket,args=("person%s" % (i),lock) )
		p.start()
区分同步和异步

在产生进程对象的时候,进程之间是异步 , 上锁之后,进程之间变成同步.

def func(num,lock):
   lock.acquire()
   print("走到上锁这个地方,变成一个同步程序,先来的进程先执行,后来的进程后执行,按次序依次执行")
   print(num)
   lock.release()
   
   
if __name__ == "__main__":
   # lock互斥锁 , 进程之间数据不共享,
   # 但是lock对象底层是通过socket来互相发送数据,不管多少个进程,都是同一个lock锁.
   lock = Lock()
   for i in range(10):
   	p = Process(target=func,args=(i,lock))
   	# 1.10个子进程异步执行,是并发操作
   	p.start()
   	

信号量 Semaphore

本质上就是锁,只不过可以控制锁的数量.

from multiprocessing import Process,Semaphore
import random,time


def ktv(person,sem):
	# 上锁
	sem.acquire()
	print("%s进入ktv唱歌" % (person))
	time.sleep( random.randrange(3,8) )
	print("%s走出ktv离开" % (person))
	# 解锁
	sem.release()


if __name__ == "__main__":
	# 同一时间最多允许4个进程执行ktv任务,剩下的进程等待.
	sem = Semaphore(4)
	for i in range(10):
		p = Process(  target=ktv,args = ("person%s" % (i) , sem)   )
		p.start()

事件 Event

阻塞事件
e = Event()生成事件对象e
e.wait()动态给程序加阻塞 , 程序当中是否加阻塞完全取决于该对象中的is_set() [默认返回值是False]
如果是True 不加阻塞
如果是False 加阻塞
控制这个属性的值
set()方法 将这个属性的值改成True
clear()方法 将这个属性的值改成False
is_set()方法 判断当前的属性是否为True (默认上来是False)
基本语法
from multiprocessing import Process , Event
import time,random


e = Event()

# 获取该对象中的成员属性值是True还是False
print(e.is_set())
print(1) 
# 用e.set()   把该对象的成员值变成True
e.set() # True
e.wait()
print(2)

# 用e.clear() 把该对象的成员值变成False
'''
e.clear()
print(3)
e.wait()
print(4)
'''
模拟红绿灯效果
# traffic_light 只做一个交通灯红绿切换的操作
def traffic_light(e):
	print("红灯亮")
	while True:
		if e.is_set():
			# 为当前绿灯等待1秒
			time.sleep(1)
			# 切换成红灯
			print("红灯亮")
			e.clear()
		else:
			# 为当前红灯等待1秒
			time.sleep(1)
			# 等完1秒之后,变成绿灯
			print("绿灯亮")
			e.set()

# 小车在遇到红灯时婷, 绿灯时行.
def car(e,i):
	if not e.is_set():
		print("car %s 在等待" % (i))
		# wait 获取的值是False , 所以阻塞
		e.wait()
	print("car %s 通行了" % (i))

# 车跑完了,但是红绿灯仍然在执行.
if __name__ == "__main__":
	e = Event()	
	# 启动交通灯
	p1 = Process(target=traffic_light,args = (e,))
	p1.start()
	
	# 开始跑车
	for i in range(20):
		time.sleep(random.randrange(0,2))
		p2 = Process(target=car,args=(e,i))
		p2.start()
  • 优化版:
# 车跑完了,红绿灯这个进程也结束掉.
if __name__ == "__main__":
	e = Event()
	lst = []	
	
	# 启动交通灯
	p1 = Process(target=traffic_light,args = (e,))
	p1.daemon = True
	p1.start()
	
	for i in range(20):
		time.sleep(random.randrange(0,2))
		p2 = Process(target=car,args=(e,i))
		p2.start()
		lst.append(p2)
	
	# 循环等待每一辆车通过红绿灯
	for i in lst:
		i.join()
		
	print("程序彻底执行结束")

队列 Queue

基本语法
"""先进先出"""
import queue
from multiprocessing import Process,Queue


q = Queue()
# 1.把数据放到q队列中 put
q.put(111)
# 2.把数据从队列里面拿出来 get
res = q.get()
print(res)
# 3.当队列里面的值都拿出来,已经没有数据的时候,在获取会阻塞.
res = q.get() 直接阻塞
# 4.get_nowait() 无论有没有都拿,如果拿不到,直接报错
"""
get_nowait 内部要依赖queue模块来实现
没有完全优化好,不推荐使用,想用就用put和get分别设置和获取.就可以了
"""
res = q.get_nowait()  不推荐使用
print(res)
# try .. except .. 捕捉异常

try:
	print(q.get_nowait())
# 特指queue.Empty 这种错误,执行某些逻辑.
except queue.Empty:
	print("empty")
except:
	print("其他")
可以使用queue 指定队列的长度
"""最多放3个,超过最大长度,就执行阻塞"""
q = Queue(3)
q.put(1)
q.put(2)
q.put(3)
# print(q.get())
# q.put(4) 阻塞的情况
# q.put_nowait(4) 超过队列最大长度,在存值直接报错 (不推荐使用)

# (了解 不常用 full empty)
"""
# 队列值满了返回真,不满返回假
res = q.full()
print(res)
# 判断队列中是否为空
res = q.empty()
print(res)
"""
进程之间的通讯
def func(q):
	# 1.主进程添加的值,子进程可以通过队列拿到.
	res = q.get()
	print("我是子进程",res)
	q.put("a2222")

	
if __name__ == "__main__":
	q1 = Queue()
	p = Process(target=func,args=(q1,))
	p.start()

	q1.get()
	#主进程添加数据
	q1.put("a111")
	p.join()  
	# 2.子进程添加的值,主进程通过队列可以拿到
	print("主进程:",q1.get())

生产者与消费者模型

在这里插入图片描述

爬虫例子:
1号进程负责获取网页中所有内容
2号进程负责匹配提取网页中的关键字

1号进程就可以看成一个生产者
2号进程就可以看成一个消费者

有时可能生产者比消费者快,反之也是一样
所以生产者和消费者为了弥补之间速度的差异化,加了一个中间的缓冲队列.

生产者和消费者模型从程序上看就是一个存放和拿取的过程.
最为理想的生产设消费者模型是两者之间的运行速度相对同步.

from multiprocessing import Process,Queue
import random,time


# 消费者模型 [负责取值]
def consumer(q,name):
	while True:
		food = q.get()
		if food is None:
			break
		time.sleep(random.uniform(0.1,1))
		print("1. %s 吃了一个%s" % (name,food))
	
# 生产者模型 [负责存值]
def producer(q,name,food):
	for i in range(5):
		time.sleep(random.uniform(0.1,1))
		print("2. %s 生产了 %s %s" % (name,food,i))
		q.put(food+str(i))
	
	
if __name__ == "__main__":
	q = Queue()
	# 创建消费者进程对象
	# 1号消费者
	c1 = Process(target=consumer,args=(q,"马俊强"))
	# 如果设置守护进程,主进程结束,当前消费者模型结束,不能够保证所有数据消费完毕.是一个弊端,并不理想.
	# c1.daemon = True
	c1.start()
	# 2号消费者
	c2 = Process(target=consumer,args=(q,"境泽年"))
	c2.start()	
	
	
	# 创建生产者进程对象
	# 1号生产者
	p1 = Process(target=producer,args=(q,"张国成","方便面"))
	p1.start()
	# 2号生产者
	p2 = Process(target=producer,args=(q,"易思","大力丸"))
	p2.start()
	
	# 等待2个生产者进程执行完毕.在向下执行.
	p1.join()
	p2.join()
	"""
	# 四个进程,2个生产者,2个消费者,添加一个None可以让其中一个消费者终止
	还有一个消费者,需要通过另一个生产者添加一个None值,来让第二个消费者终止
	所以添加两个None
	"""
	q.put(None)
	q.put(None)

JoinableQueue

put存入
get 获取
task_donejoin 通过一个中间变量统计队列元素个数
每放入一个值,队列元素个数加1
通过task_done,让当前队列的元素数量减1
最后join查找统计队列数的这个变量,如果是0,才不会添加阻塞,放行.
join判断如果队列里面还有值,默认是要加阻塞的.

用法 : 每次get一个数据,就执行一次 task_done(),可以让中间变量的值减1

基本用法
from multiprocessing import Process,JoinableQueue
import time,random


jq = JoinableQueue()
jq.put(11)
print(jq.get())
jq.task_done()
jq.join()
print("finish")
优化生产者消费者模型
# 消费者模型 [负责取值]
def consumer(q,name):
	while True:
		food = q.get()
		time.sleep(random.uniform(0.1,1))
		print("1. %s 吃了一个%s" % (name,food))
		q.task_done()
	
# 生产者模型 [负责存值]
def producer(q,name,food):
	for i in range(5):
		time.sleep(random.uniform(0.1,1))
		print("2. %s 生产了 %s %s" % (name,food,i))
		q.put(food+str(i))

if __name__ == "__main__":
	jq = JoinableQueue()
	# 创建消费者
	c1 = Process(target=consumer,args=(jq,"罗婷"))
	# 伴随主进程结束而结束
	c1.daemon = True
	c1.start()
	
	# 创建生产者
	p1 = Process(target=producer,args=(jq,"张国成","香水"))
	p1.start()
	
	p1.join()
	# 必须等队列里面的所有数据都被清空 ,判定值为0之后才放行
	jq.join()
	print("全部结束")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值