go语言中的协程

废话不多说,协程的概念什么的,就不介绍了。

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

//可以看到是按照顺序打印的

  

猜你喜欢

转载自www.cnblogs.com/traditional/p/9265737.html