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%15==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才会阻塞;