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{}) // 执行结构体方法
}