Go的Goroutine能够让我们并行的运行一些代码。但是要有效的利用它,需要一些额外的工作。当进程完成创建,我们应该能够将数据传递到正在运行的进程,我们也应该能够获取数据从正在运行的进程,channels 做到了这一点并且能够很好的与goroutines工作。
我们可以把Channel想象成一个通道管或定义的大小和容量的传输带,一边可以往管道上面存放东西,另外一边可以将其取出
Channels用来同步并发执行的函数并提供它们某种传值交流的机制。Channels的一些特性:通过channel传递的元素类型、容器(或缓冲区)和传递的方向由“<-”操作符指定。你可以使用内置函数 make分配一个channel:
1 2 3 4 5 |
i := make(chan int) // by default the capacity is 0 s := make(chan string, 3) // non-zero capacity r := make(<-chan bool) // can only read from w := make(chan<- []os.FileInfo) // can only write to |
我们可以通过<- 操作符发送或接收的数据
1 2 3 4 5 6 7 8 |
my_channel := make(chan int) //within some goroutine - to put a value on the channel my_channel <- 5 //within some other goroutine - to take a value off the channel var my_recvd_value int my_recvd_value = <- my_channel |
1 2 |
ic_send_only := make (<-chan int) //a channel that can only send data - arrow going out is sending ic_recv_only := make (chan<- int) //a channel that can only receive a data - arrow going in is receiving |
查看一个完整的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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 := 0; 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 time.Sleep(1 * 1e9) } } |
输出的结果:
1 2 3 4 5 6 |
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 |
其实并不建议将goroutine放在 for 循环中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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 //send a strawberry cake } } func receiveCakeAndPack(cs chan string) { for i := 1; i<=3; i++ { s := <-cs //get whatever cake is on the channel fmt.Println("Packing received cake: ", s) } } func main() { cs := make(chan string) go makeCakeAndSend(cs) go receiveCakeAndPack(cs) //sleep for a while so that the program doesn’t exit immediately time.Sleep(4 * 1e9) } |
对比上面的输出结果:
1 2 3 4 5 6 |
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 |
缓冲
为了避免存在一个channel的缓冲区所有读取操作都在没有锁定的情况下顺利完成(如果缓冲区是空的)并且写入操作也顺利结束(缓冲区不满),这样的channel被称作非同步的channel。下面是一个用来描述这两者区别的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func main() { message := make(chan string) count := 3 go func() { for i := 1; i <= count; i++ { fmt.Println("send message") message <- fmt.Sprintf("message %d", i) } }() time.Sleep(time.Second * 3) for i := 1; i <= count; i++ { fmt.Println(<-message) } } |
1 2 3 4 5 6 7 |
send message // waite 3 second message 1 send message send message message 2 message 3 |
现在我们提供一个缓冲区给输出信息的channel,例如:定义初始化行将被改为:message := make(chan string, 2)。这次程序输出将变为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func main() { message := make(chan string,2) count := 3 go func() { for i := 1; i <= count; i++ { fmt.Println("send message") message <- fmt.Sprintf("message %d", i) } }() time.Sleep(time.Second * 3) for i := 1; i <= count; i++ { fmt.Println(<-message) } } |
1 2 3 4 5 6 7 |
send message send message send message // wait 3 second message 1 message 2 message 3 |
这里我们看到所有的写操作的执行都不会等待第一次对缓冲区的读取操作结束,channel允许储存所有的三条信息。通过修改channel容器,我们通过可以控制处理信息的总数达到限制系统输出的目的。
死锁
1 2 3 4 5 6 |
func main() { c := make(chan int) c <- 42 // write channel val := <-c // read channel println(val) } |
我们会看到编译器报错:
1 2 3 |
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: |
这个错误就是我们所知的死锁. 在这种情况下,两个goroutine互相等待对方释放资源,造成双方都无法继续运行。GO语言可以在运行时检测这种死锁并报错。这个错误是因为锁的自身特性产生的。
代码在次以单线程的方式运行,逐行运行。向channel写入的操作(c <- 42)会锁住整个程序的执行进程,因为在同步channel中的写操作只有在读取器准备就绪后才能成功执行。然而在这里,我们在写操作的下一行才创建了读取器。
为了使程序顺利执行,我们需要做如下改动:
1 2 3 4 5 6 7 8 |
func main() { c := make(chan int) go func() { c <- 42 }() val := <-c println(val) } |
运行上面代码 就可以看到正常输出42了。
参考文献:http://golangtutorials.blogspot.hk/2011/06/channels-in-go.html
有疑问加站长微信联系(非本文作者)