Go基础——反射和并发编程goroutine

1,096 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

14反射

Go语言中反射的相关功能由内置的reflect包提供,

任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成,

并且reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的ValueType


在反射中关于类型还划分为两种:类型(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内置了诸如StoreLoadLoadOrStoreDeleteRange等操作方法。