引言
在前面几篇文章的基础上,应对并发编程中现成同步的需求场景:
我们可以使用锁,作为多线程同步的几个核心基础,实现对临界资源的保护,确保满足基本的互斥访问逻辑。
使用条件变量Condition,实现有固定顺序的复杂交互场景下的同步协作。
使用队列Queue,实现简单的消息中间件,支持生产者消费者模型,实现了多个工作环节的解耦、协作。
但是,如果资源有限的场景中,如何处理并发的同步呢?比如银行柜台只有5个窗口,最多同时服务5个客户,但是可能有20个客户在排队等待。比如,高铁站,只有6个检票通道,但是有500个旅客等待进展……
这就要用到本文的主角,信号量Semaphore了。
本文的主要内容有:
1、信号量的概念
2、信号量的使用场景
3、Python中Semaphore的设计及使用实例
信号量的概念
信号量(semaphore)是一种用于控制对共享资源访问的同步机制,多线程环境中,信号量可以用于限制同时访问某种资源的线程数量。
信号量,可以简单理解为一个计数器,它用于保护对有限资源的并发访问,通常来说这种有限资源是可以复用的,而不是消耗型的。
Python中的Semaphore可以实现信号量的支持。
信号量的使用,一般遵循如下规则:
1、每个线程在访问共享资源之前,必须先“获取”信号量,一般称为P操作,使其计数器减一。
2、使用完毕后,“释放”信号量,一般称为V操作,使计数器加一。
3、当计数器为零时,获取信号量的线程将阻塞,直到其他线程释放信号量。
信号量的使用场景
信号量的使用场景,一般是资源有限的并发场景下,对这些有限的共享资源的共享、复用。
通常可以将信号量应用于如下场景中:
1、流量控制:限制高并发场景中对某些关键部分的访问流量,防止系统过载。
2、限制有限资源的访问:信号量可以用于限制同时访问特定资源(比如数据库连接、文件读写、网络带宽等)的线程数量。
3、多生产者多消费者模式:可以用信号量协调多生产者多消费者的同步访问场景。
4、互斥锁的泛化:在某些情况下,可以将信号量看作是一个允许多个现成访问而不是单个线程的互斥锁。
基于信号量,可以更加便捷实现在实际工作中比较熟知的一些设计模式或者应用组件,比如:数据库连接池、线程池等。
Python中Semaphore的设计及使用实例
Semaphore的设计实现
首先通过Semaphore的相关文档说明,来看下Python中Semaphore的设计实现:
从Python中关于Semaphore源码可以看到该对象的设计实现:
1、通过计数器维持现有资源的数量,如果不指定资源数量,默认为1,也就退化成了一个互斥锁。
2、通过持有锁对象,确保对计数器的修改是原子操作,防止竞态条件。
3、通过持有条件变量Condition对象,来管理阻塞和唤醒等待资源的线程。
4、通过acquire()方法,实现对信号量的P操作,如果信号量的内部计数器大于0,则计数器减1;如果计数器为0,则阻塞,直至计数器大于0。
5、通过release()方法,实现对信号量的V操作,将信号量内部计数器加1,并唤醒一个阻塞在P操作上的线程(如果有的话)。
6、Semaphore对象实现了上下文管理的__enter__()和__exit__()魔术方法,所以可以通过with语法糖进行便捷的管理。
使用实例
我们通过Semaphore简单模拟一下银行柜员在柜台服务客户的业务场景:
1、有5个柜台,相应的有5个银行柜员提供业务服务。
2、共有50个客户需要在柜台办理各种业务。
直接看代码:
from threading import Thread, Semaphore
import timeclass Bank:def __init__(self, max_counter):self.counter = Semaphore(max_counter)def service(self, customer):print(f'客户{customer}需要办理业务')with self.counter:print(f'====有柜台空闲,开始接待{customer},办理相关业务===')# 模拟服务time.sleep(1)print('业务办理完成')def customer_business(bank, customer):bank.service(customer)if __name__ == '__main__':bank = Bank(5)customers = [Thread(target=customer_business, args=(bank, f'客户{i}')) for i in range(50)]for customer in customers:customer.start()for customer in customers:customer.join()
执行结果:
刚开始启动,由于只有5个柜台,所以0-4号顾客直接进行业务办理了,之后的客户要等待了。
后续有业务办理完成,柜台空闲,则继续服务后面的客户:
总结
本文首先回顾了之前介绍到的锁、条件变量和队列各自所能应对的并发同步场景,引出了资源有限的同步场景中的主角Semaphore,分别介绍了Semaphore的设计实现及使用案例。
感谢您的拨冗阅读。