下面是channel多个读端,多个写端,也是可以的
/** * 多个routine(写功能的writer)同时往一个channel里面写数据, 同时多个routine从上面这个channel(读功能的reader)里面读出数据 * * 这个例子的意义,数据来源有多个,任务的执行者多个,加快处理任务的速度 * * * */ package main import ( "log" "time" ) func main() { var shareChan = make(chan int, 3) go func() { // shareChan<-1 // shareChan<-2 // shareChan<-3 // close(shareChan) timer := time.NewTicker(3 * time.Second) // 新建一个Timer for { select { case <- timer.C: shareChan <- 7 } } }() go func() { // shareChan<-4 // shareChan<-5 // shareChan<-6 timer := time.NewTicker(1 * time.Second) // 新建一个Timer for { select { case <- timer.C: shareChan <- 2 // case <- time.After(10 * time.Second): // 这段代码永远执行不到??奇怪!! // shareChan <- 6 // } } log.Println("mmmmmmmmmmmmmmmmmmmmm") for { log.Println("tt") select { case <- time.After(10 * time.Second): shareChan <- 6 } } } // close(shareChan) }() // for { // select { // case a := <-shareChan: // log.Println(a) // } // } go func() { for i := range shareChan { log.Println("r1", i) if (i == 6) { close(shareChan) } } }() for i := range shareChan { log.Println("r2", i) if (i == 6) { } } }
1. 图解反射
在使用反射之前,此文The Laws of Reflection必读。网上中文翻译版本不少,可以搜索阅读。
开始具体篇幅之前,先看一下反射三原则:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
在三原则中,有两个关键词 interface value
与 reflection object
。有点难理解,画张图可能你就懂了。
先看一下什么是反射对象 reflection object
? 反射对象有很多,但是其中最关键的两个反射对象reflection object
是:reflect.Type
与reflect.Value
.直白一点,就是对变量类型
与值
的抽象定义类,也可以说是变量的元信息的类定义.
再来,为什么是接口变量值 interface value
, 不是变量值 variable value
或是对象值 object value
呢?因为后两者均不具备广泛性。在 Go 语言中,空接口 interface{}
是可以作为一切类型值的通用类型使用。所以这里的接口值 interface value
可以理解为空接口变量值 interface{} value
。
结合图示,将反射三原则归纳成一句话:
通过反射可以实现反射对象
reflection object
与接口变量值interface value
之间的相互推导与转化, 如果通过反射修改对象变量的值,前提是对象变量本身是可修改
的。
2. 反射的应用
在程序开发中是否需要使用反射功能,判断标准很简单,即是否需要用到变量的类型信息。这点不难判断,如何合理的使用反射才是难点。因为,反射不同于普通的功能函数,它对程序的性能是有损耗的,需要尽量避免在高频操作中使用反射。
举几个反射应用的场景例子:
2.1 判断未知对象是否实现具体接口
通常情况下,判断未知对象是否实现具体接口很简单,直接通过 变量名.(接口名)
类型验证的方式就可以判断。但是有例外,即框架代码实现中检查调用代码的情况。因为框架代码先实现,调用代码后实现,也就无法在框架代码中通过简单额类型验证的方式进行验证。
看看 grpc
的服务端注册接口就明白了。
grpcServer := grpc.NewServer()
// 服务端实现注册
pb.RegisterRouteGuideServer(grpcServer, &routeGuideServer{})
当注册的实现没有实现所有的服务接口时,程序就会报错。它是如何做的,可以直接查看pb.RegisterRouteGuideServer
的实现代码。这里简单的写一段代码,原理相同:
//目标接口定义
type Foo interface {
Bar(int)
}
dst := (*Foo)(nil)
dstType := reflect.TypeOf(dst).Elem()
//验证未知变量 src 是否实现 Foo 目标接口
srcType := reflect.TypeOf(src)
if !srcType.Implements(dstType) {
log.Fatalf("type %v that does not satisfy %v", srcType, dstType)
}
这也是grpc
框架的基础实现,因为这段代码通常会是在程序的启动阶段所以对于程序的性能而言没有任何影响。
2.2 读取结构体字段属性标签
通常定义一个待JSON解析的结构体时,会对结构体中具体的字段属性进行tag
标签设置,通过tag
的辅助信息对应具体JSON字符串对应的字段名。JSON解析就不提供例子了,而且通常JSON解析代码会作用于请求响应阶段,并非反射的最佳场景,但是业务上又不得不这么做。
这里我要引用另外一个利用结构体字段属性标签做反射的例子,也是我认为最完美诠释反射的例子,真的非常值得推荐。这个例子出现在开源项目github.com/jaegertracing/jaeger-lib
中。
用过 prometheus
的同学都知道,metric
探测标量是需要通过以下过程定义并注册的: