goroutine(协程)。
进程、线程?
进程,线程都是os层面的系统调度方式。
协程是用户层面的调用方式,利用更少的资源进行切换,而不需要system call。
但协程是调用的os的线程在执行。
当一个函数为def abc()时,使用go abc() 即为开一个协程去调用这个函数
goroutine在遇到文件i/o的时候,(线程和goroutine会与逻辑处理器)会分隔开,然后os新创建一个线程,将其绑定这个逻辑处理器接着运行,当之前的系统调用执行完成的时候,那个goroutine会放到等到队列,线程也会保存好,等待下次使用。
网络i/o稍有不同。网络i/o中,goroutine会与逻辑处理器分离,一旦该轮询器指示的某个网络读/网络写完成后,goroutine就会绑定对应的逻辑处理器来出来,处理完后又分离。
看起来线程池与协程有点像?,看下面的解释。
(协程在对堆上分配堆栈~,跟常见的上下文切换,栈保存信息有挺大区别)
协程在上下文切换的时候,信息保存在goroutine中。
Go的CSP并发模型:
1、多线程共享内存。
2、通信的方式共线数据。
goroutine在调度器调度的时候,也会出现防饿死而转到另一个goroutine的情况
package main import ( "fmt" "runtime" "sync" "time" ) func general_func(){ fmt.Println("it is bad") } func nice(){ fmt.Println("it is nice") } func main(){ fmt.Println(runtime.NumCPU()) runtime.GOMAXPROCS(1) //限制cpu核数 //wg用来等待程序完成,计数器为2表示要等待两个goroutine结束 var wg sync.WaitGroup wg.Add(2) //函数创建前加上go,即为创建goroutine go nice() //这么调用goroutine go func(){ //声明一个匿名函数(也就是可以不要函数名),并创建一个goroutine defer fmt.Println("1") //当一个函数有多个defer的时候,会像调用栈那样,先进后出 defer fmt.Println("2") // defer wg.Done() //defer xx,表示xx在该函数退出的时候调用(常用于释放资源或错误处理) for count:=0 ; count <3; count++{ for char:='a';char<'a'+4;char++{ fmt.Printf("%c\n",char) } } }() //这里加个括号是闭包函数的使用,意思为直接调用该函数。 go func(){ //声明一个匿名函数(也就是可以不要函数名),并创建一个goroutine defer fmt.Println("1") //当一个函数有多个defer的时候,会像调用栈那样,先进后出 defer fmt.Println("2") // defer wg.Done() //defer xx,表示xx在该函数退出的时候调用(常用于释放资源或错误处理) for count:=0 ; count <3; count++{ for char:='a';char<'a'+4;char++{ fmt.Printf("%c\n",char) } } }() //这里加个括号是闭包函数的使用,意思为直接调用该函数。 //sleep 2s time.Sleep(time.Duration(2)*time.Second) //如果不加这句话,也没有wg.Wait,有可能goroutine直接跑完而不显示上面的字符了 //wg.Wait() fmt.Println("it sound good") }
同步goroutine的原子函数:
原子函数底层通过加锁的访问,还挺神奇。
package main import ( "fmt" "runtime" "sync" "sync/atomic" ) //define 多个变量 var ( counter int64 wg sync.WaitGroup ) func incCounter(id int){ defer wg.Done() for count:=0;count<2;count++{ //atomic,原子函数,安全的加1 atomic.AddInt64(&counter,1) //从当前线程退出,并将其放入队列 runtime.Gosched() } fmt.Println(counter) } func main(){ wg.Add(2) //计数器+2 go incCounter(1) go incCounter(2) fmt.Println("it is good") wg.Wait() //等待计算器为0 }类似的还有LoadInt64和StoreInt64,这两个一起用来分别读和写,那么不会进入竞争状态,原子函数会保持同步
互斥锁,跟其它语言一样。
channel,用make建立,分为有缓冲和无缓冲。(buffer为了平衡读写差异,稳)
无缓冲channel需要读写均准备好,才会传输数据。(确保同时传输数据)
使用channel时,会处于阻塞状态。
Example:
/* 来自书本的一个例子,两个人玩球,分别从channel拿数据,当拿到channel被关闭时,其获胜。 当其随机数%x为0时,其失败,并且close channel */ package main import ( "fmt" "sync" "math/rand" ) //define 多个变量 var ( counter int64 wg sync.WaitGroup ) func Play(name string,buffered chan int){ defer wg.Done() //记得加上这句话,因为主goroutine需要wait 2个计数器 for { ball,ok := <-buffered if !ok { fmt.Printf("it is good,Player %s won\n",name) return } //生成一个100内的随机数 n := rand.Intn(100) if n==0 { //%7 丢球 fmt.Printf("it is bad,Player %s missed\n",name) close(buffered) //close 一个channel return } fmt.Printf("Player %s Hit %d\n",name,ball) //打中的次数 ball++ buffered<-ball } } func main(){ buffered := make(chan int) // unbuffered := make(chan string,10) wg.Add(2) //计数器+2 go Play("AAA",buffered) //channel 需要像参数一样传递 go Play("BBB",buffered) buffered <- 1 //初始为1 wg.Wait() //等待计算器为0 }对于有缓冲的buffer:
只有在目标buffer满了,发送端的channel才会阻塞;
只有在获取的buffer满了,读动作的buffer才会阻塞;