【多线程】信号量(Semaphore)常见的应用场景
【多线程】信号量(Semaphore)常见的应用场景
本文来自于我关于多线程系列文章。欢迎阅读、点评与交流
1.【多线程】信号量(Semaphore)是什么?
2.【多线程】信号量(Semaphore)常见的应用场景
信号量(Semaphore)是一种非常重要的同步机制,由荷兰计算机科学家Dijkstra提出。它的核心是一个计数器,用于控制多个线程(或进程)对有限数量共享资源的访问。
信号量的主要应用场景可以分为以下几大类:
1. 互斥(Mutex / Binary Semaphore)
这是最基础、最常见的应用场景。二进制信号量(其值只能为0或1)常被用作互斥锁,以确保在任何时刻只有一个线程可以访问临界区。
-
核心思想:保护一段代码(临界区),防止多个线程同时进入。
-
工作方式:
- 信号量初始值为1,表示锁是可用的。
- 线程进入临界区前执行
wait()
(或P()
)操作,将信号量减为0。 - 其他线程再执行
wait()
时会被阻塞,因为信号量已经是0。 - 拥有锁的线程退出临界区后执行
signal()
(或V()
)操作,将信号量恢复为1,唤醒一个等待的线程。
-
应用实例:
- 多线程操作共享数据:例如,多个线程同时更新一个全局变量、一个链表或一个共享文件。使用信号量可以避免数据竞争和不一致。
- 访问共享硬件:如只有一个打印机,多个进程需要排队打印。
2. 同步(Synchronization)
信号量可以用于线程或进程间的执行顺序同步,确保一个操作必须在另一个操作完成之后才能开始。这通常使用计数信号量(初始值常为0)。
-
核心思想:一个线程等待另一个线程完成某个任务后发出的“信号”。
-
工作方式:
- 线程A需要等待线程B完成某项工作。
- 信号量初始化为0。
- 线程A在需要等待的地方执行
wait()
,由于初始值为0,它会被阻塞。 - 线程B完成任务后,执行
signal()
,将信号量加1。 - 线程A被唤醒,继续执行。
-
经典模型:生产者-消费者问题
- 场景:一个或多个生产者线程生产数据并放入缓冲区,一个或多个消费者线程从缓冲区取出数据消费。
- 需要的信号量:
- 互斥信号量(mutex):初始值为1,用于保证任何时候只有一个线程(生产者或消费者)操作缓冲区。
- 空位信号量(empty):初始值为缓冲区的总大小(N),表示当前可用的空位数量。生产者生产前需要获取一个空位(
wait(empty)
),消费者消费后会释放一个空位(signal(empty)
)。 - 满位信号量(full):初始值为0,表示当前缓冲区中可供消费的数据项数量。消费者消费前需要获取一个满位(
wait(full)
),生产者生产后会释放一个满位(signal(full)
)。
-
其他应用实例:
- 线程A等待线程B完成初始化。
- 主线程等待所有工作线程完成任务后再进行汇总。
3. 控制对资源池的访问(Resource Pool)
当有一组数量固定的同类资源(如数据库连接池、内存缓冲区块、线程池中的工作线程)时,可以使用计数信号量来管理对这些资源的分配。
- 核心思想:信号量的初始值设置为资源的总数。
- 工作方式:
- 线程需要资源时,执行
wait()
,信号量值减1。如果值大于0,立即获取资源;如果值等于0,则阻塞直到有资源被释放。 - 线程释放资源时,执行
signal()
,信号量值加1,并唤醒一个可能正在等待资源的线程。
- 线程需要资源时,执行
- 应用实例:
- 数据库连接池:系统启动时创建10个数据库连接。信号量初始值为10。任何需要数据库操作的线程必须先从信号量获取一个“许可”,使用完毕后释放。
- 内存池管理。
4. 限制并发数(Throttling / Rate Limiting)
这个场景与控制资源池类似,但目的略有不同。它不是为了管理具体的物理资源,而是为了限制系统某一部分的负载,防止系统过载。
- 核心思想:设置一个并发上限。
- 工作方式:
- 信号量初始值设置为允许的最大并发数(例如100)。
- 每当一个任务开始执行时,先执行
wait()
。 - 当任务执行完毕时,执行
signal()
。 - 如果当前已有100个任务在运行,第101个任务在执行
wait()
时会被阻塞,直到有任务完成。
- 应用实例:
- 限制同时处理的HTTP请求数量,防止服务器崩溃。
- 限制同时发起的API调用次数,以遵守第三方服务的速率限制。
总结
应用场景 | 信号量类型 | 初始值 | 核心目的 |
---|---|---|---|
互斥(Mutex) | 二进制信号量 | 1 | 保护临界区,一次只允许一个线程进入 |
同步(如生产者-消费者) | 计数信号量 | 0 或 N | 协调线程间的执行顺序 |
资源池管理 | 计数信号量 | 资源总数 | 高效、安全地分配有限资源 |
限制并发数 | 计数信号量 | 并发上限 | 流量控制,防止系统过载 |
重要提示:在现代编程中(如Java的 synchronized
关键字、java.util.concurrent.locks.ReentrantLock
或Python的 threading.Lock
),实现互斥时更推荐使用专门的互斥锁(Mutex),因为它们通常具有所有权概念(防止其他线程释放锁)等更安全的特性。而信号量(特别是计数信号量)在实现复杂的同步模式(如生产者-消费者)和控制并发访问数量方面,仍然具有不可替代的优势。