Go 中的 Channels
Goroutines 允许你以并行的方式运行程序,但是要让这种并行有实际的用途,我们还有一些其它的需求——我们应该能够向运行中的某一个进程传递数据,并且也应该可以从一个运行中的进程中获取数据,这就需要使用到Go的Channels了。
一个Channel可以被想象成为一个定义了大小和能力的管道或传送带,我们可以将数据从它的一侧放入,然后在另一侧取出。我们拿蛋糕工厂中的传送带来打比方的话,Channel就是那个传送带,有一台电脑负责制作蛋糕,它将做好的蛋糕放到传送带上面来,然后另一台负责打包的电脑就在等待传送带上面的蛋糕,当它等到一个制作好的蛋糕之后就将其从传送带上面取出然后打包进行下一个处理,在这个过程中,传送带(也就是我们的Channel)起到了让负责制作和打包的两台电脑通讯的功能。
传送带就可以被称之为Channel
+++++++++++++++++++++++++++++++++ + 打好包的蛋
制作电脑 -> -> 蛋糕 -> -> 蛋糕 -> -> 蛋糕 -> 打包电脑 -> -> ++ 糕,可以进
+++++++++++++++++++++++++++++++++ + 行其它处理在Go中,关键字 chan 被用来定义Channel,make 关键字则被用来创建它,并且定义它能传送的数据类型(是传送蛋糕的传送带而不是传送鸡蛋的)。
ic := make(chan int)
sc := make(chan string)
mc := make(chan mytype)你可以使用 <- 操作符来向Channel传送或者取回数据,比如:
ic := make(chan int)
//在某些Goroutines中向其传送数据:
ic <- 5
//在另一些Goroutines中取出其中的数据
var recv int
recv = <- ic你还可以使用 <- 操作符定义Channel中数据的移动方向:
sendOnlyChan := make( <- chan int) //只能发送数据
recvOnlyChan := make( chan -< int) //只能接收数据一个Channel可以容纳数据的容量是十分分重要,它表示了多少个项目可以被同时存储在Channel中,就比如制作蛋糕的电脑一分钟可以制作十个蛋糕,但是打包的电脑却只能一分钟打包五个,这样就会有太多的蛋糕因为无法来得急打包而制作电脑又源源不断的传送带上面放新制作好的蛋糕而被丢掉浪费,这在计算机并行运算中,就是所谓的生产者-消费者同步问题(当然了,Channel是不移动的)。
如果一个Channel的容量是 1,那么当一份数据被发送到该Channel之后,就必须要等待这份数据被取出之后,下一份数据才能再被发送进来,发送者与接收者每一次只能在一个时间传送一份数据,而且任何一方都必须要等待另一方执行完成之后才能传送或者接收下一个份数据。我们的示例将从同步Channel开始。
我们到现在为止所定义的任何一个Channel都是同步的——新数据必须在旧数据被取走后才能传送进去,现在让我们来看看这个蛋糕制作工厂在Go是如何实现的:
package main
import (
"fmt"
"time"
"strconv"
)
var i int
func makeCakeAndSend(cs chan string) {
i = i + 1
cakeName := "Strawberry Cake " + strconv.Itoa(i)
fmt.Println("Making a cake and sending...", cakeName)
cs <- cakeName // send a strawberry cake
}
func receiveCakeAndPack(cs chan string) {
s := <- cs // get whatever cake is on the channel
fmt.Println("Packing received cake: ", s)
}
func main() {
cs := make(chan string)
for i := 1; i < 3; i++ {
go makeCakeAndSend(cs)
go receiveCakeAndPack(cs)
}
// Sleep for a while so that the program doesn't exit immediately and output is clear for illustration
wait, _ := time.ParseDuration("2s")
time.Sleep(wait)
}输出结果为:
Making a cake and sending ... Strawberry Cake 1
Packing received cake: Strawberry Cake 1
Making a cake and sending ... Strawberry Cake 2
Packing received cake: Strawberry Cake 2
Making a cake and sending ... Strawberry Cake 3
Packing received cake: Strawberry Cake 3上面的代码中,我们进行了三次请求来制作一个蛋糕,并立马将其打包,。因为我们的生产方(makeCakeAndSend)与消费方(receiveCakeAndPack)是同步的,所以在生产方生产好蛋糕之后必须等待消费方接收数据,它才能再一次往Channel中发送数据。
现在我们来改变一下下代码,通常的,Goroutines是一个在一直重复着运行的代码块,它们对数据进行操作并且通过Channels与其它的Goroutines进行数据的传送,在下一个示例中,我们将上面示例的循环移动到Goroutine里面去,这样我们只请求Goroutine一次:
package main
import (
"fmt"
"time"
"strconv"
)
func makeCakeAndSend(cs chan string) {
for i := 1; i<=3; i++ {
cakeName := "Strawberry Cake " + strconv.Itoa(i)
fmt.Println("Making a cake and sending...", cakeName)
cs <- cakeName
}
}
func receiveCakeAndPack(cs chan string) {
for i := 1; i<=3; i++ {
s := <- cs
fmt.Println("Packing received cake: ", s)
}
}
func main() {
cs := make(chan string)
go makeCakeAndSend(cs)
go receiveCakeAndPack(cs)
wait, _ := time.ParseDuration("5s")
time.Sleep(wait)
}输出为:
Making a cake and sending... Strawberry Cake 1
Making a cake and sending... Strawberry Cake 2
Packing received cake: Strawberry Cake 1
Packing received cake: Strawberry Cake 2
Making a cake and sending... Strawberry Cake 3
Packing received cake: Strawberry Cake 3上面的输出只是在我电脑上面的,但是你的并不一定也是这样一模一样的输出,由于我们将循环移到了Goroutines里面,所以对于 makeCakeAndSend 和 receiveCakeAndPack 会分别制作三个蛋糕并传送到Channel上和取出三个蛋糕并打包。
评论已关闭