标签:理解 例子 span hello wak 高效 nil 特性 and
在上一篇教程中,我们讨论了如何使用协程实现并发。在这篇教程中,我们将讨论信道以及如何使用信道实现协程间通信。
信道(Channel)可以被认为是协程之间通信的管道。与水流从管道的一端流向另一端一样,数据可以从信道的一端发送并在另一端接收。
每个信道都有一个与之关联的类型。此类型是允许信道传输的数据类型,除此类型外不能通过信道传输其他类型。
chan T
是一个 T
类型的信道。
信道的 0 值为 nil
。值为 nil
的信道变量没有任何用处,我们需要通过内置函数 make
来创建一个信道,就像创建map和 slice一样。
下面的代码声明了一个信道:
1 package main 2 3 import "fmt" 4 5 func main() { 6 var a chan int 7 if a == nil { 8 fmt.Println("channel a is nil, going to define it") 9 a = make(chan int) 10 fmt.Printf("Type of a is %T", a) 11 } 12 }
因为信道的 0 值为 nil
,因此第 6 行声明的信道 a
的值为 nil
。因此执行 if
里面的语句创建信道。上面的程序中 a
是一个 int
类型的信道。程序的输出为:
channel a is nil, going to define it Type of a is chan int
像往常一样,速记声明也是定义信道的一种有效而简洁的方式:
a := make(chan int)
上面的这行代码同样定义了一个 int
型的信道。
通过信道发送和接收数据的语法如下:
data := <- a // read from channel a a <- data // write to channel a
箭头的指向说明了数据是发送还是接收。
在第一行,箭头的方向是从 a
向外指,因此我们正在从信道 a
中读取数据并将读取的值赋值给变量 data
。
在第二行,箭头的方式是指向 a
,因此我们正在向信道 a
中写入数据。
通过信道发送和接收数据默认是阻塞的。这是什么意思呢?当数据发送给信道后,程序流程在发送语句处阻塞,直到其他协程从该信道中读取数据。同样地,当从信道读取数据时,程序在读取语句处阻塞,直到其他协程发送数据给该信道。
信道的这种特性使得协程间通信变得高效,而不是向其他编程语言一样,显式的使用锁和条件变量来达到此目的。
理论到此为止:) 让我们通过一个程序来理解协程之间如何使用信道进行通信。
我们将用信道来重写在上一篇教程中的一个例子。
如下是那篇教程中的一个例子:
package main import ( "fmt" "time" ) func hello() { fmt.Println("Hello world goroutine") } func main() { go hello() time.Sleep(1 * time.Second) fmt.Println("main function") }
这是上一篇教程中的例子,我们通过使用 Sleep
来使主协程休眠,以等待 hello
协程执行结束。如果你不明白这是为什么,请阅读上一篇教程
我们用信道重写上面的程序,如下:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func hello(done chan bool) { 8 fmt.Println("Hello world goroutine") 9 done <- true 10 } 11 func main() { 12 done := make(chan bool) 13 go hello(done) 14 <-done 15 fmt.Println("main function") 16 }
在上面的程序中,我们在第 12 行定义了一个 bool 类型的信道 done
,然后将它作为参数传递给 hello
协程。在第 14 行,我们从信道 done
中读取数据。程序将在这一行被阻塞直到其他协程向信道 done
里写入数据,在未读取到数据之前程序将在这一行一直等待而不会执行下一行语句。因此这里消除了在原程序中使用 time.Sleep
来阻止主协程退出的必要。
<-done
这一行从信道 done
中读取数据,但是没有使用该数据,也没有将它赋值给其他变量,这是完全合法的。
现在我们的 main
协程被阻塞,等待从信道 done
中读取数据。hello
协程接受信道 done
作为参数,打印 Hello world goroutine
然后将数据写入信道 done
中。当写入完毕后,main
协程从信道 done
中接收到数据,main
协程解除阻塞,继续执行下一条语句,打印:main function
。
程序的输出为:
Hello world goroutine
main function
让我们修改上面程序,在 hello
协程中加入一个休眠,来更好的理解阻塞的概念。
1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func hello(done chan bool) { 9 fmt.Println("hello go routine is going to sleep") 10 time.Sleep(4 * time.Second) 11 fmt.Println("hello go routine awake and going to write to done") 12 done <- true 13 } 14 func main() { 15 done := make(chan bool) 16 fmt.Println("Main going to call hello go goroutine") 17 go hello(done) 18 <-done 19 fmt.Println("Main received data") 20 }
在上面程序中的第 10 行,我们在 hello
函数中增加了4 秒钟的休眠。
该程序首先打印 Main going to call hello go goroutine
。然后 hello
协程开始执行,它将打印 hello go routine is going to sleep
,然后 hello
协程休眠 4 秒,在这期间, main
协程由于在等待从信道 done
中读取数据而始终阻塞(在<-done
这一行)。4 秒中之后, hello
协程打印:hello go routine awake and going to write to don
,接着 main
协程打印:Main received data
。
让我们再写一个例子来更好的理解信道。该程序打印一个数字的每一位的平方和与立方和,并将平方和与立方和相加得出最后的结果。
例如,输入123 ,程序将做如下计算以得出最后结果:
squares = (1 * 1) + (2 * 2) + (3 * 3)
cubes = (1 * 1 * 1) + (2 * 2 * 2) + (3 * 3 * 3)
output = squares + cubes = 49
我们将平方和的计算与立方和的计算分别放在一个协程中执行,最后在主协程中将它们的计算结果求和。
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func calcSquares(number int, squareop chan int) { 8 sum := 0 9 for number != 0 { 10 digit := number % 10 11 sum += digit * digit 12 number /= 10 13 } 14 squareop <- sum 15 } 16 17 func calcCubes(number int, cubeop chan int) { 18 sum := 0 19 for number != 0 { 20 digit := number % 10 21 sum += digit * digit * digit 22 number /= 10 23 } 24 cubeop <- sum 25 } 26 27 func main() { 28 number := 589 29 sqrch := make(chan int) 30 cubech := make(chan int) 31 go calcSquares(number, sqrch) 32 go calcCubes(number, cubech) 33 squares, cubes := <-sqrch, <-cubech 34 fmt.Println("Final output", squares + cubes) 35 }
在第 7 行,函数 calcSquares
计算 number
每一位的平方和,并将结果发送给信道 squareop
。同样地,在第 17 行,函数calcCubes
计算 number
每一位的立方和,并将结果发送给信道 cubeop
。
这两个函数接受不同的信道作为参数,并分别运行在各自的协程中(第31行和32行),最后将结果写入各自的信道。主协程在第 33 行同时等待这两个信道中的数据。一旦从这两个信道中接收到数据,它们分别被存放在变量 squares
和 cubes
中,最后将它们的和打印出来。程序的输出为:
Final output 1536
标签:理解 例子 span hello wak 高效 nil 特性 and
原文地址:https://www.cnblogs.com/liuzhongchao/p/9133212.html