一个典型的并发问题:
多个 goroutine 都要用 MQTT 客户端,但客户端只能初始化一次。
这时候不能只用一个 bool 表示:
initializing bool
因为布尔值只能说明“有人在初始化”,但不能告诉别人:
初始化结束了吗?
成功还是失败?
客户端对象在哪里?
我要怎么等它完成?
更合适的做法,是把“初始化结果”包装成一个 Future。
type ClientFuture struct {
done chan struct{}
client mqtt.Client
err error
}
func (f *ClientFuture) Wait(ctx context.Context) (mqtt.Client, error) {
select {
case <-f.done:
return f.client, f.err
case <-ctx.Done():
return nil, ctx.Err()
}
}
可以把它想成:
第一个 goroutine 去厨房做饭;
其他 goroutine 不重复做饭,只在门口等;
饭做好后,关一下铃铛;
所有等的人同时知道:饭好了,或者做失败了。
在代码里,这个“铃铛”就是 done channel。
第一个调用方完成 MQTT 初始化后,写入 client 和 err,然后关闭 done。
future.client = client
future.err = err
close(future.done)
所有等待中的 goroutine 都会被唤醒,并拿到同一份初始化结果。
这里不是说 channel 一定比锁高级。
锁负责保护“谁来创建这个 Future”;
channel 负责通知“初始化已经完成”。
锁解决互斥问题,channel 解决等待和广播问题。
关键不是工具哪个好,而是语义要对。