废话不多说,协程的概念什么的,就不介绍了。
go语言如何创建协程
package main import ( "fmt" "time" ) func test(){ for { fmt.Println("这是新开的协程") time.Sleep(time.Second) } } func main(){ //go语言创建协程非常简单,只需要在函数前面加上一个go关键字即可 go test() for { fmt.Println("这是主任务") time.Sleep(time.Second) } } //可以看到是并发执行的 //这是主任务 //这是新开的协程 //这是新开的协程 //这是主任务 //这是主任务 //这是新开的协程 //这是新开的协程 //这是主任务 //这是新开的协程 //这是主任务 //这是主任务 //这是新开的协程 //这是新开的协程 //这是主任务 //这是新开的协程 //这是主任务 //这是主任务 //这是新开的协程 //这是新开的协程 //这是主任务 //这是主任务 //这是新开的协程 //这是主任务 //这是新开的协程
如果主协程先退出的话,子协程会如何
package main import ( "fmt" "time" ) //主协程退出了,其他协程也要跟着退出,类似于python中的守护线程 func main(){ go func() { fmt.Println("古明地盆") time.Sleep(time.Second) }() i:=0 for { fmt.Println("i=",i) i++ if i==4{ break } } fmt.Println("主协程退出了") } //可以看到主协程退出之后,子协程也跟着结束了 //古明地盆 //i= 0 //i= 1 //i= 2 //i= 3 //主协程退出了
协程是时间片轮转, 每个任务执行一下。
package main import "fmt" func main(){ go func() { for i:=0;i<5;i++ { fmt.Println("satori") } }() for i:=0;i<3;i++{ fmt.Println("mashiro") } } //可以看到打印三次mashiro之后就退出了,为什么呢?因为协程一般默认会先执行主协程,因此在还没有轮到子协程打印,主协程就已经执行完毕退出了 //mashiro //mashiro //mashiro
我们可以让主协程休息一下
package main import ( "fmt" "time" ) func main() { go func() { for i := 0; i < 5; i++ { fmt.Println("satori") } }() for i := 0; i < 3; i++ { time.Sleep(time.Second) fmt.Println("mashiro") } } //可以看到,satori已经全部打印完毕,而主协程每隔一秒打印一次mashiro //satori //satori //satori //satori //satori //mashiro //mashiro //mashiro
当然在go语言中,我们可以通过协程调度的方式。
使用一个叫runtime的一个包
package main import ( "fmt" "runtime" ) func main() { go func() { for i := 0; i < 5; i++ { fmt.Println("satori") } }() for i := 0; i < 3; i++ { runtime.Gosched() fmt.Println("mashiro") } } //satori //satori //satori //satori //satori //mashiro //mashiro //mashiro
程序的结果是satori瞬间被打印了出来 这是因为runtime主动将协程的控制权交了出去,让其他的协程先执行 我们这里只有一个子协程,因此会瞬间打印五次satori 然后子协程执行完毕,开始执行主协程 此外,子协程和主协程都是瞬间打印的。 上面采用主协程睡眠的方式,主协程会每个一秒打印一次
runtime.Goexit的使用
runtime.Goexit()的作用是终止协程
package main import "fmt" func test(){ defer fmt.Println("3") fmt.Println("2") } func main(){ go func() { fmt.Println("1") test() fmt.Println("4") }() //目的是为了让主协程不要停 for { } } //分析:首先会打印1,然后执行test函数,打印2,然后打印3。注意:defer在哪个函数内定义,哪个函数执行完之后之后defer //说白了就是最内层的函数。尽管defer处于匿名函数里面,但是它是定义在test函数里面的,因此当test函数执行完毕,就会执行defer //最后打印4 //运行结果 //1 //2 //3 //4
package main import ( "fmt" ) func test(){ defer fmt.Println("3") return fmt.Println("2") } func main(){ go func() { fmt.Println("1") test() fmt.Println("4") }() //目的是为了让主协程不要停 for { } } //1 //3 //4
这里打印了1 3 4,很好理解,打印1,执行test函数,然后在test中return,2没被打印,于是执行3,然后执行4
但是如果我将return换成runtime.Goexit(),上面说过runtime.Goexit()作用是终止协程。
package main import ( "fmt" "runtime" ) func test(){ defer fmt.Println("3") runtime.Goexit() fmt.Println("2") } func main(){ go func() { fmt.Println("1") test() fmt.Println("4") }() //目的是为了让主协程不要停 for { } } //1 //3
可以看到只打印了1和3,这次连4也没打印。原因是,test()中的runtime.Goexit()一旦执行,整个协程就退出了,所以4没有被打印
如果test函数前面不加go关键字,或者被调用的时候不在协程里面,会报错
go.GOMAXPROCS
此函数是用来设置以多少核cpu进行运算
package main import ( "runtime" "fmt" ) func main(){ //指定以一个核运行,这个函数有一个返回值,表示当前的机器的cpu核数 runtime.GOMAXPROCS(1) for { go fmt.Println(1) fmt.Println(0) } }
为了更直观的观察到现象,这里使用终端执行
可以看到会打印很多的0,然后打印很多的1
如果我们指定用4核去跑
package main import ( "runtime" "fmt" ) func main(){ //指定以一个核运行,这个函数有一个返回值,表示当前的机器的cpu核数 runtime.GOMAXPROCS(4) for { go fmt.Print(1) fmt.Print(0) } }
可以看到0和1之间交错的频率变高了,这是因为指定了4核,同时向终端输出
多资源竞争问题
package main import ( "fmt" "time" ) func pprint(str string){ for _,data := range str{ fmt.Printf("%c",data) time.Sleep(time.Second) } } func main(){ go func() {pprint("satori")}() go func() {pprint("mashiro")}() for {} } //smaastohiriro //可以看到打印了这么个结果,这就是协程之间资源竞争的结果。都使用同一个打印函数,交叉进行。
如果想避免资源竞争问题,需要使用管道。
基本概念不介绍,只说一句,管道传递的是指针
通过channel实现同步
package main import ( "fmt" ) // 创建一个管道类型的全局变量,如果不指定管道的容量,那么默认是0 // 1表示只能放一个,再放第二个的时候,就必须等待消费者拿走,才可以继续执行下面的代码,否则会卡住 // 0表示一个都放不下,当生产者放进去一个的时候就会卡住,直到有消费者过来取走数据 // ch <- var 表示将var放在管道里 // var := <- ch,表示将ch管道里面的值取走,并赋值给var var ch = make(chan int) func pprint(str string){ for _,data := range str{ fmt.Printf("%c",data) fmt.Println() //这里不再采用睡眠的方式 } } func main(){ go func() { pprint("satori") //在没有往管道里面写值的时候,下面的任务是不会执行的 fmt.Println("我没执行完,你别给我执行啊,听到没") //当此任务执行完毕之后,往管道里面写完值之后,下面立刻感受到,因为用的是同一个管道,从而把值取出来,执行函数 ch <- 123 }() go func() { //由于没有数据肯定会卡住.从管道里取出的值也可以不赋给某一个变量,如果不赋值,就直接丢弃掉了 <- ch pprint("mashiro") }() for {} } //s //a //t //o //r //i //我没执行完,你别给我执行啊,听到没 //m //a //s //h //i //r //o //可以看到是按照顺序打印的