直接receive数据,将会出现死锁:
package main
import (
"fmt"
)
func main (){
goo(32)
}
func goo(s int) {
counter := make(chan int)
counter <- s
fmt.Println(<-counter)
}
在上面的示例中,向unbuffered channel中send数据的操作counter <- s
是在main goroutine中进行的,从此channel中recv的操作<-counter
也是在main goroutine中进行的。send的时候会直接阻塞main goroutine,使得recv操作无法被执行,go将探测到此问题,并报错:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
要修复此问题,只需将send操作放在另一个goroutine中执行即可:
package main
import (
"fmt"
)
func main() {
goo(32)
}
func goo(s int) {
counter := make(chan int)
go func() {
counter <- s
}()
fmt.Println(<-counter)
}
或者,将counter设置为一个容量为1的buffered channel:
counter := make(chan int,1)
这样放完一个数据后send不会阻塞(被recv之前放第二个数据才会阻塞),可以执行到recv操作。
unbuffered channel同步通信示例
下面通过sync.WaitGroup类型来等待程序的结束,分析多个goroutine之间通信时状态的转换。因为创建的channel是unbuffered类型的,所以send和recv都是阻塞的。
package main
import (
"fmt"
"sync"
)
// wg用于等待程序执行完成
var wg sync.WaitGroup
func main() {
count := make(chan int)
// 增加两个待等待的goroutines
wg.Add(2)
fmt.Println("Start Goroutines")
// 激活一个goroutine,label:"Goroutine-1"
go printCounts("Goroutine-1", count)
// 激活另一个goroutine,label:"Goroutine-2"
go printCounts("Goroutine-2", count)
fmt.Println("Communication of channel begins")
// 向channel中发送初始数据
count <- 1
// 等待goroutines都执行完成
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating the Program")
}
func printCounts(label string, count chan int) {
// goroutine执行完成时,wg的计数器减1
defer wg.Done()
for {
// 从channel中接收数据
// 如果无数据可recv,则goroutine阻塞在此
val, ok := <-count
if !ok {
fmt.Println("Channel was closed:",label)
return
}
fmt.Printf("Count: %d received from %s \n", val, label)
if val == 10 {
fmt.Printf("Channel Closed from %s \n", label)
// Close the channel
close(count)
return
}
// 输出接收到的数据后,加1,并重新将其send到channel中
val++
count <- val
}
}
上面的程序中,激活了两个goroutine,激活这两个goroutine后,向channel中发送一个初始数据值1,然后main goroutine将因为wg.Wait()等待2个goroutine都执行完成而被阻塞。
再看这两个goroutine,这两个goroutine执行完全一样的函数代码,它们都接收count这个channel的数据,但可能是goroutine1先接收到channel中的初始值1,也可能是goroutine2先接收到初始值1。接收到数据后输出值,并在输出后对数据加1,然后将加1后的数据再次send到channel,每次send都会将自己这个goroutine阻塞(因为unbuffered channel),此时另一个goroutine因为等待recv而执行。当加1后发送给channel的数据为10之后,某goroutine将关闭count channel,该goroutine将退出,wg的计数器减1,另一个goroutine因等待recv而阻塞的状态将因为channel的关闭而失败,ok状态码将让该goroutine退出,于是wg的计数器减为0,main goroutine因为wg.Wait()而继续执行后面的代码。
使用for range迭代channel
前面都是在for无限循环中读取channel中的数据,但也可以使用range来迭代channel,它会返回每次迭代过程中所读取的数据,直到channel被关闭。
例如,将上面示例中的printCounts()改为for-range的循环形式。
func printCounts(label string, count chan int) {
defer wg.Done()
for val := range count {
fmt.Printf("Count: %d received from %s \n", val, label)
if val == 10 {
fmt.Printf("Channel Closed from %s \n", label)
close(count)
return
}
val++
count <- val
}
}
多个"管道":输出作为输入
channel是goroutine与goroutine之间通信的基础,一边产生数据放进channel,另一边从channel读取放进来的数据。可以借此实现多个goroutine之间的数据交换,例如goroutine_1->goroutine_2->goroutine_3
,就像bash的管道一样,上一个命令的输出可以不断传递给下一个命令的输入,只不过golang借助channel可以在多