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, 已经取走快递。

我的问题:
这是我最开始的实现,但是我发现是有问题的。

  1. 比如寄快递的goroutine 2里判断 订单还没有完成,然后发送cancel信号。但是在这两个步骤中间,可能发生订单因为timeout,或者deliver导致订单已经完成了。那么 goroutine 2 发出的 cancel信号就一直没人收了,就会一直卡在那儿。同理,快递员 goroutine 3也会遇到类似的问题。
  2. 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回答

ccmouse

2020-07-07

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:

     ...

  }

}

0
1
new_chapter
多谢您的回答!
2020-07-18
共1条回复

Google资深工程师深度讲解Go语言 由浅入深掌握Go语言

语法+分布式爬虫实战 为转型工程师量身打造

5995 学习 · 1909 问题

查看课程