二月二理发店太火了,家家都爆满,这次我们来到Hilzer的理发店,这是一家比较大的理发店。
这家理发店有三位有经验的理发师,每个理发师都有自己独立的理发椅。等待区有一个四座的沙发,等待区还有站一些人等待。还有一个收银台。
- 依照新冠疫情防控的要求,最多允许20个顾客进入。
- 如果有顾客,理发师优先从沙发上叫起等待最久的顾客开始理发;如果没有顾客,理发师则在自己的理发椅上睡觉
- 顾客来到后
- 如果已经有20个顾客,则此顾客离开去其它理发店
- 如果理发师在睡觉,则叫起一位理发师开始理发
- 如果没有空闲的理发师,则选择沙发坐下
- 如果沙发已经坐满,则在休息去站着等待
- 理完发后顾客去收银台去缴费,交完费后理发师把发票交给顾客。顾客就离开,而理发师则准备服务下一位顾客
这一次理发店的问题比上一个理发店问题要复杂很多:
- 等待区分为了站立区和沙发区
- 新增加了收银台,理发师和顾客需要付款和发票的相互等待
- 三位理发师
这类问题使用信号量并发原语比较好,因为理发师、沙发等都是一定数量的资源。理发师和顾客之间缴费和发票之间的同步按理说使用channel比较合适,但是收银台只有一位收银员,还需要处理理发师、顾客和收银员之间的并发,所以我们可以把收银、开发票看成计数器是1的资源。
首先我们还是定义信号量和辅助方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| type Semaphore chan struct{} func (s Semaphore) Acquire() { s <- struct{}{} } func (s Semaphore) Release() { <-s } func randomPause(max int) { time.Sleep(time.Millisecond * time.Duration(rand.Intn(max))) }
|
接下来我们定义所需的并发原语。
我们使用customerMutex
这个排他锁来控制理发店的顾客数量。
使用sofaSema
控制站立用户坐到沙发上以及理发师叫起等待的顾客。
paySema
用来让顾客去缴费,而receiptSema
让理发师把发票送给顾客,顾客离开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var ( customerMutex sync.Mutex customerMaxCount = 20 customerCount = 0 sofaSema Semaphore = make(chan struct{}, 4) ) var ( barberSema Semaphore = make(chan struct{}, 3) ) var ( paySema Semaphore = make(chan struct{}, 1) receiptSema Semaphore = make(chan struct{}, 1) )
|
接下来我们实现理发师的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func barber(name string) { for { log.Println(name + "老师尝试请求一个顾客") sofaSema.Release() log.Println(name + "老师找到一位顾客,开始理发") randomPause(2000) log.Println(name + "老师理完发,等待顾客付款") paySema.Acquire() log.Println(name + "老师给付完款的顾客发票") receiptSema.Release() log.Println(name + "老师服务完一位顾客") } }
|
接下来实现顾客的逻辑, 注意customerCount
操作需要使用Mutex保护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| func customers() { for { randomPause(500) go customer() } } func customer() { customerMutex.Lock() if customerCount == customerMaxCount { log.Println("没有空闲座位了,一位顾客离开了") customerMutex.Unlock() return } customerCount++ customerMutex.Unlock() log.Println("一位顾客开始等沙发坐下") sofaSema.Acquire() log.Println("一位顾客找到空闲沙发坐下,直到被理发师叫起理发") paySema.Release() log.Println("一位顾客已付完钱") receiptSema.Acquire() log.Println("一位顾客拿到发票,离开") customerMutex.Lock() customerCount-- customerMutex.Unlock() }
|
最后把所有的逻辑串起来:
1 2 3 4 5 6 7 8 9 10 11 12 13
| func main() { go barber("Tony") go barber("Kevin") go barber("Allen") go customers() sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs }
|
运行这个程序观察并发的执行。
通过这个问题,我们可以好好的练习Semaphore的使用,以及两个goroutine之间信号的传递,控制并发程序的协作执行。
下一章你想了解什么样的经典并发问题呢?
历史并发问题