1、Goroutine协程:协程就是go提供的轻量级的独立运算过程,比线程还轻;创建一个协程,就是使用go关键字,后面跟上要运行的函数
案例要求:1)、先计算1000以下所有能够被3整除的整数的和A
2)、然后计算1000以下所有能够被5整除的整数和B
3)、然后再计算1000以下所有能够被3和5整除的整数和C
4)、使用A+B-C得到最后结果
案例中,使用go关键字创建一个协程,协程函数内使用channel(chan数据,类似队列,在取chan中数据时,如果为空,会阻塞,直到chan中有数据)存储数据
package main import ( "fmt" "runtime" "time" ) func get_sum_of_divisible(num int, divider int, resultChan chan int) { sum := 0 for value := 0; value < num; value++ { if value%divider == 0 { sum += value } } resultChan <- sum } func main() { runtime.GOMAXPROCS(runtime.NumCPU()) LIMIT := 1000 //用于被15除结果 job1 := make(chan int, 1) //用于被3,5除结果 job2 := make(chan int, 2) t_start := time.Now() go get_sum_of_divisible(LIMIT, 15, job1) go get_sum_of_divisible(LIMIT, 3, job2) go get_sum_of_divisible(LIMIT, 5, job2) sum15 := <-job1 sum3, sum5 := <-job2, <-job2 sum := sum3 + sum5 - sum15 t_end := time.Now() fmt.Println(sum) fmt.Println(t_end.Sub(t_start)) }
2、Channel通道:提供了协程之间的通信方式以及运行同步机制
案例:假设训练定点投篮和三分投篮,教练在计数
如果向channel里面写信息,必须有配对的取信息的一端;假如把下面的go count(c)注释掉,则不会有打印数据
package main import ( "fmt" "strconv" "time" ) func fixed_shooting(msg_chan chan string) { i := 1 for { msg_chan <- "fixed shooting" var str string = strconv.Itoa(i) fmt.Println("continue fixed shooting..." + str) i++ } } func count(msg_chan chan string) { for { msg := <-msg_chan fmt.Println(msg) time.Sleep(time.Second * 1) } } func main() { var c chan string c = make(chan string) go fixed_shooting(c) go count(c) var input string fmt.Scanln(&input) }
加一个三分投篮方法,可以看出,写入channel中的数据,必须读取出来才能再次写入,如果没有读取出来,再次写入会失败
package main import ( "fmt" "strconv" "time" ) func three_point_shooting(msg_chan chan string) { i := 1 for { var str string = strconv.Itoa(i) msg_chan <- "three point shooting--" + str i++ } } func fixed_shooting(msg_chan chan string) { i := 1 for { var str string = strconv.Itoa(i) msg_chan <- "fixed shooting--" + str i++ } } func count(msg_chan chan string) { for { msg := <-msg_chan fmt.Println(msg) time.Sleep(time.Second * 1) } } func main() { var c chan string c = make(chan string) go fixed_shooting(c) go three_point_shooting(c) go count(c) var input string fmt.Scanln(&input) }
结果图如下,结果是交替输出的,也就是channel中输入的必须输出后才能再次输入
3、Channel通道方向
var c chan string 可读写channel,可写入也可读取
var c chan<- string 只写channel,只能写入
var c <-chan string 只读channel,只能读取
4、多通道(Select)
案例中,select依次检查每个channel是否有消息传递过来,如果有就输出;如果同时有多个消息到达,select随机选择一个channel来从中读取消息;如果没有一个channel有消息到达,那么select语句就阻塞在这里一直等待。select 语句也可以有default片段,case不符合时会执行default语句片段。select中case、default类似switch中case、default
下面case中使用了time.After,可以这样理解,main函数也是一个channel,time.After让main阻塞指定时间后,读出时间消息(案例中没有用变量存储返回的时间)
package main import ( "fmt" "time" ) func fixed_shooting(msg_chan chan string) { var times = 3 var t = 1 for { if t <= times { msg_chan <- "fixed shooting" } t++ time.Sleep(time.Second * 1) } } func three_point_shooting(msg_chan chan string) { var times = 3 var t = 1 for { if t <= times { msg_chan <- "three point shooting" } t++ time.Sleep(time.Second * 1) } } func main() { c_fixed := make(chan string) c_3_point := make(chan string) go fixed_shooting(c_fixed) go three_point_shooting(c_3_point) go func() { for { select { case msg1 := <-c_fixed: fmt.Println(msg1) case msg2 := <-c_3_point: fmt.Println(msg2) case <-time.After(time.Second * 5): fmt.Println("timeout check again...") } } }() var input string fmt.Scanln(&input) }
结果图
5、Channel Buffer通道缓冲区:一般我们定义的channel都是同步的,也就是说接受端和发送端彼此等待对方OK才开始。但是如果执行了channel缓冲区大小,那么消息的发送和接收是异步的,除非channel缓冲区已经满了
package main import ( "fmt" "strconv" "time" ) func shooting(msg_chan chan string) { var group = 1 for { for i := 1; i <= 10; i++ { msg_chan <- strconv.Itoa(group) + ":" + strconv.Itoa(i) } group++ time.Sleep(time.Second * 5) } } func count(msg_chan chan string) { for { fmt.Println(<-msg_chan) } } func main() { var c = make(chan string, 20) go shooting(c) go count(c) var input string fmt.Scanln(&input) }