本文已参与「新人创作礼」活动,一起开启掘金创作之路
14反射
在Go语言中反射的相关功能由内置的reflect包提供,
任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,
并且reflect包提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的Value和Type。
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)
==typeof拿到变量具体的类型信息==
而具体的类型信息又分为两种:==一种是kind,一种是Name==
==kind针对复合数据类型==比如:
Array // 数组 Chan // 通道 Func // 函数 Interface // 接口 Map // 映射 Ptr // 指针 Slice // 切片 String // 字符串 Struct // 结构体 UnsafePointer // 底层指针
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。
14.1type of
import (
"fmt"
"reflect"
)
type cat struct{}
type dog interface {}
func justifyType(x interface{}){
v := reflect.TypeOf(x)
fmt.Printf("origin:%v, 'reflect.TypeOf':%v, 'Name':%v, 'Kind':%v\n", x, v, v.Name(), v.Kind())
}
func main(){
fmt.Println("***********")
var x int32 = 300
justifyType(x)
var y float32 = 2.1415
justifyType(y)
var z = make(map[string]int, 3)
justifyType(z)
var s = "this is string"
justifyType(s)
fmt.Println("***********")
//结构体类型
var c = cat{}
justifyType(c)
//slice
var a []int
justifyType(a)
var b []string
justifyType(b)
}
14.2value of
func reflectValue(x interface{}){
v := reflect.ValueOf(x)
valueType := v.Kind()
switch valueType {
case reflect.Float64:
fmt.Println("float64:", float64(v.Float()))
case reflect.Float32:
fmt.Println("float32:",float32(v.Float()))
case reflect.Bool:
fmt.Println("bool")
case reflect.Int32:
fmt.Println("int32")
}
}
func reflectValue2(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
}
}
var e int32 = 45
reflectValue(e)
var f float32 = 2.265
reflectValue(f)
var ff float64 = 2.2165
reflectValue(ff)
14.3通过反射设置变量的值
你既然要设置变量的值,那么肯定要先获取变量的值,先用reflect.ValueOf()
注意:这里必须传指针类型,因为函数传参为值拷贝,传值的话,修改的是传进函数里的参数的拷贝的参数,是没有意义的。修改的是副本,reflect包会引发panic
==反射中使用专有的Elem()方法来获取指针对应的值==
// 通过反射设置变量的值
func setValue(x interface{}){
v := reflect.ValueOf(x)
k := v.Elem().Kind()
switch k{
case reflect.Float64:
v.Elem().SetFloat(3.154)
}
}
func SetValue2(x interface{}) {
v := reflect.ValueOf(x)
// 反射中使用 Elem()方法获取指针对应的值
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(200)
}
}
14.4通过反射取结构体中字段
使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。
type student struct{
Name string `json:"name" ini:"s_name"`
Score int `json:"score" ini:"s_score"`
}
func main(){
stu := student{
Name: "大黄",
Score: 95,
}
v := reflect.TypeOf(stu)
fmt.Printf("name:%v kind:%v\n", v.Name(), v.Kind())
for i := 0; i < v.NumField(); i++ {
fildObj := v.Field(i)
fmt.Printf("name:%v type:%v tag:%v\n", fildObj.Name, fildObj.Type, fildObj.Tag)
fmt.Println(fildObj.Tag.Get("json"), fildObj.Tag.Get("ini"))
}
//根据名字取结构体中字段
fildObj2, ok := v.FieldByName("Score")
if ok{
fmt.Printf("name:%v type:%v tag:%v\n", fildObj2.Name, fildObj2.Type, fildObj2.Tag)
}
}
14.5通过反射获取结构体中方法的函数
type student struct{
Name string `json:"name" ini:"s_name"`
Score int `json:"score" ini:"s_score"`
}
// Sleep 注意首字母要大写
func (s student)Sleep()string{
msg := "后来烟雨落盛京,一人撑伞两人行"
fmt.Println(msg)
return msg
}
func (s student)Heart()string{
msg := "深深的话我们浅浅的说,长长的路我们慢慢的走"
fmt.Println(msg)
return msg
}
//通过反射获取结构体中方法的函数
func printMethod(x interface{}){
fmt.Println("---------------in------------")
t := reflect.TypeOf(x)
fmt.Println(t.NumMethod())
// t 因为要拿到具体的方法去调用,所以用的reflect.ValueOf(x)
v := reflect.ValueOf(x)
for i := 0; i < v.NumMethod(); i++ {
methodObj := v.Method(i)
fmt.Print("methodObj:", methodObj, "\t")
fmt.Print("methodObj.Type:", methodObj.Type(), "\t")
fmt.Print("methodName:", t.Method(i).Name, "\n")
arg := []reflect.Value{}
methodObj.Call(arg)
}
//通过方法名获取函数
mt := reflect.TypeOf(x)
m, ok := mt.MethodByName("Heart")
if ok{
fmt.Println(m)
fmt.Println(m.Type)
}
}
15.goroutine并发
有的时候只会打印出来一个main,是因为执行太快了,导致main退出了,新开启的还没有执行
var wg sync.WaitGroup
func hello(){
fmt.Println("\nhello")
wg.Done()
}
func mainn(){
//go hello()
//fmt.Println("main--")
// cpu硬等,消耗资源
//time.Sleep(time.Second)
wg.Add(1)
go hello()
fmt.Println("main--")
wg.Wait()
}
15.2单向通道
发送完数据时,我们可以通过close函数来关闭通道。
chan<- int是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;<-chan int是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。
//<-chan:只能从里面取数据
//chan<-:数据只能发送进去
// ch1只能发送数据
func f1(ch1 chan<- int){
for i := 0; i < 100; i++ {
ch1<-i
}
close(ch1)
}
//从ch1中取值,平方, 发送到ch2
func f2(ch1 <-chan int, ch2 chan<- int){
for{
tmp, ok := <-ch1
if !ok{
break
}
ch2 <- tmp*tmp
}
close(ch2)
}
15.3==work pool池==
可以指定启动的goroutine数量–worker pool模式,控制goroutine的数量,防止goroutine泄漏和暴涨。
func worker(id int, jobs <-chan int, results chan<- int){
for job := range jobs{
fmt.Printf("%dworker start ---%d\n", id, job)
time.Sleep(time.Microsecond*500)
fmt.Printf("%dworker end-----%d\n", id, job)
results <- job*2
}
//close(results)
}
func main(){
jobs := make(chan int, 100)
results := make(chan int, 100)
//开启三个goroutine
for i := 1; i <= 3; i++ {
go worker(i, jobs, results)
}
//开启五个任务
for i := 1; i <= 5; i++ {
jobs <- i
}
close(jobs)
//for result := range results{
// fmt.Println(result)
// //fmt.Println(v)
//}
for i := 1; i <= 5; i++ {
ret := <-results
fmt.Println(ret)
}
}
15.4select多路复用
select的使用类似于switch语句,它有一系列case分支和一个默认的分支。每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句。
func main(){
var res chan int
res = make(chan int , 1)
for i := 1; i <= 10; i++ {
select {
case x := <-res:
fmt.Println(x)
case res<-i:
default:
fmt.Println("do nothing")
}
}
}
15.5锁
15.5.1互斥锁
是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁
var (
x int64
wg sync.WaitGroup
lock sync.Mutex
)
func add() {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
for i:=0; i<2;i++{
go add()
}
wg.Wait()
fmt.Println(x)
}
15.5.2读写互斥锁
读写锁分为两种:==读锁和写锁。==当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
涉及读写场景时,用读写锁,比用互斥锁性能高
var (
x int
wg sync.WaitGroup
lock sync.Mutex
rwlock sync.RWMutex
)
func read(){
//lock.Lock()
rwlock.RLock()
time.Sleep(time.Microsecond)
//lock.Unlock()
rwlock.RUnlock()
wg.Done()
}
func write(){
//lock.Lock()
rwlock.Lock()
x = x + 1
time.Sleep(10*time.Microsecond)
//lock.Unlock()
rwlock.Unlock()
wg.Done()
}
func main(){
start := time.Now()
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
wg.Wait()
fmt.Println(x)
fmt.Println(time.Now().Sub(start))
}
15.6sync.Once
确保某些操作在高并发的场景下只执行一次,例如只加载一次配置文件、只关闭一次通道
sync.Once只有一个Do方法
var icons map[string]image.Image
var loadIconsOnce sync.Once
func loadIcons() {
icons = map[string]image.Image{
"left": loadIcon("left.png"),
"up": loadIcon("up.png"),
"right": loadIcon("right.png"),
"down": loadIcon("down.png"),
}
}
// Icon 是并发安全的
func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons)
return icons[name]
}
15.7sync.Map
并发安全的MAP
Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。