一个典型的并发问题:

多个 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 初始化后,写入 clienterr,然后关闭 done

future.client = client
future.err = err
close(future.done)

所有等待中的 goroutine 都会被唤醒,并拿到同一份初始化结果。

这里不是说 channel 一定比锁高级。

锁负责保护“谁来创建这个 Future”;
channel 负责通知“初始化已经完成”。

锁解决互斥问题,channel 解决等待和广播问题。

关键不是工具哪个好,而是语义要对。