golang 并发、反射知识笔记

1. 性能降低,但是高效率的并发安全map
import (
	"sync"
)
func main(){
	var mymap sync.Map
	for i:=0;i<100;i++{
		go writeM(mymap,i,i+1)
		go readM(mymap,i)
	}
}

func readM(mymap sync.Map,key,int)int{
	if v,ok :=mymap.Load(key);ok{ // 用Load来获取
		return v.(int) // 获取的v是接口,需要强制转换成指定类型
	}
	return -1
}

func writeM(mymap sync.Map,key int,value int){
	mymap.Store(key,value) // 用Store来存储
}


知识点1:

    线程有固定的栈,基本都是2 MB,都是固定分配的;这个栈用于保存局部变量,在函数切换时使用。但是对于goroutine这种轻量级的协程来说,一个大小固定的栈可能会导致资源浪费,所以Go采用了动态扩张收缩的策略,初始化为2 KB,最大可扩张到1 GB。
    首先,线程切换需要陷入内核,然后进行上下文切换,而协程在用户态由协程调度器完成,不需要陷入内核,这样代价就小了。其次,协程的切换时间点是由调度器决定,而不是由系统内核决定的,尽管它们的切换点都是时间片超过一定阈值,或者是进入I/O或睡眠等状态时。

知识点2: runtime包

runtime.Gosched()
使当前的Go协程放弃处理器(CPU),让其它协程运行

// fmt.Println() 函数输出时需要占用CPU
// 不同协程调用fmt时需要抢占CPU
func main(){
	go func(){
		// 匿名函数
		for i:=0;i<3;i++{
			fmt.Println("go:",i)
		}
	}()
	
	for i:=0;i<2;i++{
		fmt.Println("main",i)
	}
	// output : main 0
	//          main 1
	// 因为抢占CPU,导致协程没有抢占到
}

func main(){
	go func(){
		// 匿名函数
		for i:=0;i<3;i++{
			fmt.Println("go:",i)
		}
	}()
	// 调用下面的语句,让main协程放弃处理器,使得上面的协程拥有处理器
	// 并且该协程使用完,才轮到main协程
	runtime.Gosched()
	for i:=0;i<2;i++{
		fmt.Println("main",i)
	}
	// output : 
	// go:0  go:1 go:3 节约篇幅,其实是换行
	//          main 0
	//          main 1
	// 因为抢占CPU,导致协程没有抢占到
}

// runtime.Goexit() 用来终止当前协程继续运行
// n :=runtime.GOMAXPROCS(4) 设置使用几个CPU

知识点3

channel
CSP模型:通信顺序模型,描述两个并发实体用channel进行通信,通过通信来共享内存,而不是通过共享内存来通信。
channel的零值是nil
channel的接收和发送
ch :=make(chan int)
ch <- 10
x := <- ch // 从channel接收需要用等号
<- ch 可以没有接受者

无缓冲通道,需要接收和发送同时准备好,否则就会goroutine阻塞
这种通道任意一个操作都无法离开另一个单独存在

有缓存通道,最大的区别在于,接收和发送操作可以不同步

func main(){
	// 无缓冲通道
	ch :=make(chan int)
	
	go func(){
		for i:=0;i<3;i++{
			fmt.Println(len(ch)," ",cap(ch))
			ch <- i
		}
	}()
	
	for i:=0;i<3;i++{
		time.Sleep(time.Second) //先休息一秒,让通道先写入,
		fmt.Println(<-ch) // 然后读取
	}
	
	// output:
	// 0 0  然后协程那边发送到ch,然后阻塞协程
	// 1 主协程休眠1秒后,从ch中接受,同时go协程不再阻塞
	// 0 0  然后go协程继续执行for循环
	// 2
}
// 有缓存通道
func main(){
	ch :=make(chan int,3)
	go func(){
		for i:=0;i<3;i++{
			fmt.Println(len(ch)," ",cap(ch))
			ch <- i
			//除非ch已满才会阻塞
		}
	}()
	
	for i:=0;i<3;i++{
		time.Sleep(time.Second) //休息一秒就能让上面的go协程执行完毕
		fmt.Println( <- ch )
	}
	
	//output 
	//0 3
	//1 3
	//2 3
	//0
	//1
	//2
}

知识点3

close和range在channel的使用
: 作用是为了让接收者知道,在没有新的数据需要接收,收到close才会执行后面的v,ok := <- ch;ok{} 操作。或者 range操作
ch在被关闭后,就不能往ch发送,但是可以从ch中接收
使用<-的操作,返回两个值,v,ok;ok为false表示channel被关闭
v,ok := <- ch 如果ch被关闭,ch就为false
如果ch为nil,则返回定义的零值,如int 0,string “”

package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 3) //有缓冲通道
	go func() {
		fmt.Println("当前在go协程里")
		for i := 0; i < 3; i++ {
			fmt.Println(len(ch), " ", cap(ch))
			ch <- i
		}
		close(ch) //直接关闭通道,无缓冲的ch也可以被close
	}()
	nums := 0
	for {
		if nums == 0 {
			fmt.Println("main线程抢占CPU,执行打印")
		}
		if v, ok := <-ch; ok { // 检测到ch还没有显示被close,就阻塞main线程
			fmt.Println("main打印被close的ch:", v)
		} else {
			break
		}
		nums++
	}

	// 此时ch是被close状态,不能再发送数据到ch,但任然可以从ch中接收到空值
	// 输出是0,此时ch为nil,0是int的默认值
	fmt.Println(<-ch)
	
	// output:
	// main线程抢占CPU,执行打印
	// 当前在go协程里
	// 0   3
	// 0   3
	// 1   3
	// main打印被close的ch: 0
	// main打印被close的ch: 1
	// main打印被close的ch: 2
	// 0
}

// 更简洁的遍历channel,使用range,当close(ch) 才退出range
func main(){
	ch :=make(chan int,3)
	go func(){
		for i:=0;i<3;i++{
			ch <- i	
		}
		close(ch)
	}()
	for v :=range ch{ 
		// 当ch没有被显示关闭,则一直阻塞,不断读取ch
		// 直到ch被关闭,才开始执行下面的语句
		// 如果ch被读取完毕,但是没有close,会报error		
		fmt.Println(v)
	}
}

知识点4 单向channel

// chan<-  接收channel
// <-chan 发送channel
//	var ch chan int // 双向的channel
//	var chin chan<- int //只接收的channel
//	var chout <-chan int //只发送的channel
func consumer(in <-chan int){
	// 消费者
	for v := range in{
		fmt.Println(v)
	}
}
func main(){
	ch :=make(chan int)//一个双向的ch,可以隐式赋值给单向channel,反过来NO
	
	// 生产者-消费者模型
	go func(out chan<- int){ //生产者
		// 只接收的channel
		for i:=0;i<3;i++{
			out <- i
		}
		close(ch)
	}(ch)
	consumer(ch) //双向付给单向
}

select的作用:监听channel上的数据流动,每个case都必须是原子操作
selcet按顺序评估每一个case
先是从上往下执行,直到不能执行
如果每个都能被执行,则随机选择一个执行
如果没有一条能执行,则执行default
如果没有default,则阻塞select,直到case有一条能执行

func main(){
	ch1 :=make(chan int) // 无缓冲管道
	select{
	case str := <- ch1:
		fmt.Println(str)
	case ch1 <- 1:
		fmt.Println("写入")
	default:
		fmt.Println("没有管道能执行")
	}
	// 上面的select只执行一次

	select{
	}
	// 这里会永久阻塞
}

锁的机制
如何用channel实现锁

// sync的Mutex互斥锁
import (
	"sync"
)
var mu sync.Mutex // 互斥锁
func write(){
	mu.Lock()
	defer mu.UnLock()
	//保证一次只有一个线程能够进入临界区
}

var Rmu sync.RWMutex // 读写锁,同时读,不能同时写
// RLock() RUnlock() 读锁定, 读解锁
// Lock() Unock()  写锁定和解锁
func write(){
	Rmu.RLock() 
	defer mu.Unlock()
	//可以多个线程能够进入临界区读
}
//写锁需要阻塞写锁:一个协程拥有写锁时,其他协程写锁定需要阻塞
//写锁需要阻塞读锁:一个协程拥有写锁时,其他协程读锁定需要阻塞
//读锁需要阻塞写锁:一个协程拥有读锁时,其他协程写锁定需要阻塞
//读锁不能阻塞读锁:一个协程拥有读锁时,其他协程也可以拥有读锁

// sync.WaitGroup 并发控制
func main(){
	var wg sync.WaitGroup
	for i:=0;i<3;i++{
		wg.Add(1) // 表示计数器加1个goruntine
		go func(){
			fmt.Println("123")
			wg.Done() //goroutine执行结束后将计数器减1
		}()
	}
	wg.Wait() //主goroutine阻塞等待计数器变为0
	// 等到计数器变为0 才会接着执行
}

context :可以控制更多的goruntine
可以控制一组呈树状结构的goroutine,每个goroutine拥有相同的上下文

//

反射相关知识 reflect
golang在运行时更新和检查 变量 的值,及相关操作
通过反射获取变量的类型和值

var m reflect.Type=reflect.TypeOf(x) 获取Type类型
var m reflect.Value=reflect.ValueOf(x) 获取Value类型
n :=reflect.ValueOf(x) 获取值
反射的种类Kind:包括int、string、struct等等
m.Kind() == reflect.Int 来判断是否类型是Int
m.Kind() == reflect.Ptr 判断是不是指针类型,t.Elem()获取元素类型

import (
	"fmt"
	"reflect"
)

// 获取结构体成员的数量 reflect.Type的NumField()
// 根据索引返回成员变量 Field()
type Person struct{
	Name string
	Age int `json:"age"`
	string
}

func (p Person)GetName(){
	fmt.Println(p.Name)
}

func main(){
	pers := Person{Name:"123",10,"备注"}
	typeOfPerson :=reflect.TypeOf(pers)
	typeOfPerson.NumFiled()  // 返回3,结构体成员数量
	field := typeOfPerson.Field(0) // 返回第0个字段也就是Name
	// 字段名称,字段标签,是否是匿名字段
	fmt.Println(field.Name,field.Tag,field.Anonymous)
	
	// field,ok :=typeOfPerson.FieldByName("Age") 查找字段并返回
	
	// 通过反射执行结构体方法
	valueOfPerson :=reflect.ValueOf(pers)
	
	// 根据名字反射来获取方法
	f :=valueOfPerson.MethodByName("GetName")
	f.Call([]reflect.Value{}) // 执行结构体方法
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值