select 阻塞在多个channel的问题
来源:11-2 使用Channel等待任务结束

new_chapter
2020-07-03
老师您好,我最近在练习go并发,遇到一个问题一直想不明白,希望您能帮我解惑。
假设一个具体的场景,
goroutine 1 代表一个快递柜,里面寄存货物等待快递员来收取。订单要么被快递员取走,要么被取消,或者超时。然后,对快递柜加锁做减少occupation的操作。
goroutine 2 代表寄快递的,在订单被取消的时候发一个cancel的signal去通知goroutine 1.
goroutine 3 代表一个快递员,在一个大概的时间范围内到达并取走快递,发deliver信号通知goroutine 1, 已经取走快递。
我的问题:
这是我最开始的实现,但是我发现是有问题的。
- 比如寄快递的goroutine 2里判断 订单还没有完成,然后发送cancel信号。但是在这两个步骤中间,可能发生订单因为timeout,或者deliver导致订单已经完成了。那么 goroutine 2 发出的 cancel信号就一直没人收了,就会一直卡在那儿。同理,快递员 goroutine 3也会遇到类似的问题。
- goroutine1 和 3, 都有一个定时器,如果谁先完成就去通知另外一个goroutine。刚好同时完成就死锁了。
每个order都有自己的cancel/deliver channel, 是为了能在外部结束这个order对应的goroutine.
是不是这样的场景不适合用select阻塞来实现呢?
# goroutine 1
select {
case <- order.deliver:
// do deliver
break
case <- order.cancel:
// do cancel
break
case <- time.After(timeout):
// do decay
break
}
# goroutine 2
if orderCancelled {
order.cancel <- struct{}{}
// do cancel
}
# goroutine 3
select {
case <- time.After(RandomDuration)
order.deliver <- struct{}{}
// do deliver
}
1回答
-
select一般是套在for里面,无条件循环下去的。
goroutine1中,快递柜不需要加锁,因为快递柜是goroutine1中的局部变量,只有goroutine1在访问。这就是goroutine+channel带来的很大好处。我们的快递柜需要维护orderId到order状态。
订单超时的问题不能简单的用一个time.After来解决,因为我们要知道哪个订单超时了。我们可以再开一个goroutine,扫描所有订单,把超时的挑出来,通过channel像deliver/cancel一样的方法发送给goroutine1
另外,go的case不需要break
最后,类似这样:
# goroutine 1
for {
select {
case <- order.deliver:if !orderDelivered(orders, order.deliver.id) {
// do deliver
}
case <- order.cancel:
if !orderCancelled(orders, order.cancel.id) {// do cancel
}
case <- order.decay:...
}
}
012020-07-18
相似问题