• Golang关键字-select


    一、Select解决什么问题?

    在这里插入图片描述
    在Golang中,两个协程之间通信Channel(图一),在接受协程中通过代码表示即为

    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:
            }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上述代码,创建两个通道,通过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")
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这种写法特别常见,起一个协程,阻塞循环监听多个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
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    上述代码,通过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:
            }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这段代码的意思是,程序执行到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()
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    上述代码中,有两个地方用到select,一个地方是非阻塞,检查contex是否被取消;另外一个是阻塞等待;

    三、select原理

    在这里插入图片描述
    如果想了解select的实现,可以阅读runtime.selectgo代码。它主要包含三部分:

    首先,检查一下是否有准备就绪的channel(多个channel就绪,随机选择一个),如果有,就执行;
    其次,将当前goroutine包装成sudog,挂载到对应的channel上;
    最后,如果channel中数据准备就绪,唤醒该协程继续执行第一步逻辑;

    【注】源码细节,感兴趣并且有需求可以深入了解,但是不要陷入源码的怪圈中;

    总结

    本文主要讲述下面三部分内容:

    从Go源码开发者的角度考虑,为什么需要select?
    介绍了select常用的两种写法,一种是非阻塞的,一种是阻塞的,以及开源项目如何使用它们;
    介绍了select的基本实现;

    参考

    Go语言设计与实现
    gprc-go
    go-zero

  • 相关阅读:
    Android 多线程、线程池
    Vue3属性绑定方式分析
    web技术——HTML文档基础部分(1)
    Java注解详解和自定义注解实战,用代码讲解
    Elasticsearch中的评分排序--Function score query
    Office登录一直转圈Win10怎么解决?
    MySQL主从复制和读写分离
    机器学习笔记 九:预测模型优化(防止欠拟合和过拟合问题发生)
    [100天算法】-面试题 17.17.多次搜索(day 43)
    使用GIt小组分工完成积分商城系统-SSM
  • 原文地址:https://blog.csdn.net/m0_73728511/article/details/134037710