1. 概述
并发编程中,理发师问题(Sleeping Barber Problem) 是一个非常经典且具有代表性的同步与资源调度问题。这个问题最早由 Dijkstra 在 1965 年提出,用于说明多个进程之间如何协作与竞争资源。问题的设定来源于一个理发店的场景,涉及多个顾客和一个理发师之间的交互。
2. 问题定义
我们设想一个理发店有以下配置:
- 一位理发师
- 一把理发椅
- 一个有 N 把椅子的等候区
下图展示了这个理发店的基本结构:
顾客与理发师的行为特征如下:
✅ 理发师:
- 没有顾客时睡觉
- 有顾客到来时被唤醒,开始理发
✅ 顾客:
- 进入理发店后,如果理发师空闲,直接坐下理发
- 如果理发师正忙,就去等候区找空椅子坐下
- 如果等候区已满,顾客直接离开
⚠️ 核心问题在于:
如何在多线程环境下,正确协调顾客和理发师之间的行为,避免出现竞争条件(race condition)和死锁。
下图展示了问题的核心逻辑流程:
3. 解决方案
为了解决该问题,我们需要使用 信号量(Semaphore) 来控制并发访问,确保资源的互斥与同步。
使用的信号量如下:
信号量 | 含义 |
---|---|
customers |
等待理发的顾客数量 |
barbers |
理发师是否准备好服务顾客 |
mutex |
互斥锁,用于保护共享资源(如等候区计数器) |
常量定义和信号量初始化(伪代码):
function ConstantsAndSyncPrimitives():
CHAIRS <- 5
customers <- 0
barbers <- 0
mutex <- 1
num_waiting <- 0
理发师线程逻辑(伪代码):
function BarberRoutine():
while true:
sem_wait(customers) // 没有顾客就等待
sem_wait(mutex) // 进入临界区
num_waiting <- num_waiting - 1
sem_post(barbers) // 唤醒顾客,准备理发
sem_post(mutex)
cut_hair() // 理发
顾客线程逻辑(伪代码):
function CustomerRoutine():
sem_wait(mutex) // 进入临界区
if num_waiting < CHAIRS: // 是否有空位
num_waiting <- num_waiting + 1
sem_post(customers) // 增加顾客信号量
sem_post(mutex) // 退出临界区
sem_wait(barbers) // 等待理发师
get_haircut() // 接受服务
else:
sem_post(mutex) // 没位置就离开
leave_shop()
小结:
customers
信号量通知理发师有顾客到来barbers
信号量通知顾客理发师已准备好mutex
保证对num_waiting
的访问是线程安全的
这个模型可以很好地映射到现实中的排队系统,例如线程池、任务队列、网络请求处理等场景。
4. 总结
理发师问题虽然是一个简化模型,但其核心思想广泛适用于各种并发与调度场景。通过使用信号量机制,我们成功地协调了顾客与理发师之间的行为,避免了资源竞争和死锁问题。
理解这个问题有助于我们更好地掌握并发编程中资源调度和线程同步的核心机制,是学习操作系统和并发编程的必经之路。