关键字"/>
Golang关键字
一、Select解决什么问题?
在Golang中,两个协程之间通信Channel(图一),在接受协程中通过代码表示即为<ch;如果协程需要监听多个Channel,只要有其中一个满足条件,就执行相应的逻辑(图二),这种select的应用场景之一,代码如下:
func TestSelect(t *testing.T) {ch1 := make(chan int, 1)ch2 := make(chan int , 1)select {case <-ch1:fmt.Println("ch1")case <-ch2:fmt.Println("ch2")default:}
}
上述代码,创建两个通道,通过select监听协程是否有数据,如果有打印相应的值,如果没有通过default结束程序运行;
二、Select常用用法
循环阻塞监测
func TestLoopSelect(t *testing.T) {ch1 := make(chan int, 1)ch2 := make(chan int, 1)go func() {// 每隔1s发送一条消息到channel中for range time.Tick(1 * time.Second) {ch1 <- 1}}()for {select {case <-ch1:fmt.Println("ch1")case <-ch2:fmt.Println("ch2")}}
}
这种写法特别常见,起一个协程,阻塞循环监听多个channel,如果有数据执行对应的操作。比如说
// go-zero/core/discov/internal/registry.go
func (c *cluster) watchStream(cli EtcdClient, key string, rev int64) bool {var rch clientv3.WatchChanif rev != 0 {rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix(),clientv3.WithRev(rev+1))} else {rch = cli.Watch(clientv3.WithRequireLeader(c.context(cli)), makeKeyPrefix(key), clientv3.WithPrefix())}for {select {case wresp, ok := <-rch:...c.handleWatchEvents(key, wresp.Events)case <-c.done:return true}}
}
上述代码,通过for + select阻塞循环监测注册中心数据是否有变换,有变化的话,针对变化类型,执行对应逻辑;
非阻塞监控
func TestSelect(t *testing.T) {ch1 := make(chan int, 1)ch2 := make(chan int , 1)select {case <-ch1:fmt.Println("ch1")case <-ch2:fmt.Println("ch2")default:}
}
这段代码的意思是,程序执行到select,就检查一下channel里面是否有数据,有就处理,没有就退出;在grpc-go中,
// grpc-go/clientconn.go
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {cc := &ClientConn{target: target,conns: make(map[*addrConn]struct{}),dopts: defaultDialOptions(),czData: new(channelzData),}defer func() {select {case <-ctx.Done():switch {case ctx.Err() == err:conn = nilcase err == nil || !cc.dopts.returnLastError:conn, err = nil, ctx.Err()default:conn, err = nil, fmt.Errorf("%v: %v", ctx.Err(), err)}default:}}()if cc.dopts.scChan != nil {// Blocking wait for the initial service config.select {case sc, ok := <-cc.dopts.scChan:if ok {cc.sc = &sccc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})}case <-ctx.Done():return nil, ctx.Err()}}}
上述代码中,有两个地方用到select,一个地方是非阻塞,检查contex是否被取消;另外一个是阻塞等待;
三、select原理
如果想了解select的实现,可以阅读runtime.selectgo代码。它主要包含三部分:
首先,检查一下是否有准备就绪的channel(多个channel就绪,随机选择一个),如果有,就执行;
其次,将当前goroutine包装成sudog,挂载到对应的channel上;
最后,如果channel中数据准备就绪,唤醒该协程继续执行第一步逻辑;
【注】源码细节,感兴趣并且有需求可以深入了解,但是不要陷入源码的怪圈中;
总结
本文主要讲述下面三部分内容:
从Go源码开发者的角度考虑,为什么需要select?
介绍了select常用的两种写法,一种是非阻塞的,一种是阻塞的,以及开源项目如何使用它们;
介绍了select的基本实现;
参考
Go语言设计与实现
gprc-go
go-zero
更多推荐
Golang关键字
发布评论