Golang关键字select的常用用法总结
作者:林欣快滚去学习
这篇文章主要为大家详细介绍了golang中select关键字的常用用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
一、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.WatchChan if 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 = nil case 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 = &sc cc.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的基本实现;
以上就是Golang关键字select的常用用法总结的详细内容,更多关于go select的资料请关注脚本之家其它相关文章!