package main
func main() {
done := make(chan struct{}) // 结束事件
c := make(chan string) // 数据传输通道
go func() {
s := <-c // s接收来自c的数据
println(s)
close(done) // 关闭通道, 作为结束通知
}()
c <- "hi" // 发送消息
<-done // 阻塞, 直到有数据或管道关闭
}
package main
func main() {
c := make(chan string, 3) //创建带3个缓冲槽的异步通道
c <- "hi" // 缓冲区未满, 不会阻塞
c <- "word"
println(<-c) // 缓冲区尚有数据, 不会阻塞
println(<-c)
println(<-c) // 缓冲区没有数据了, 产生死锁.
}
//hi
//word
//fatal error: all goroutines are asleep - deadlock!
//goroutine 1 [chan receive]:
package main
// 使用ok-idom模式处理
func main() {
done := make(chan struct{})
c := make(chan int)
go func() {
defer close(done) // 确保发出结束通知
for {
x, ok := <-c
if !ok { // 据此判断通道是否关闭
return
}
println(x)
}
}()
c <- 1
c <- 2
c <- 3
close(c)
<-done
}
package main
func main() {
done := make(chan struct{})
c := make(chan int)
go func() {
defer close(done)
for x := range c { // 循环获取消息, 直到通道被关闭
println(x)
}
}()
c <- 1
c <- 2
c <- 3
close(c)
<-done
}
及时用close函数关闭通道引发结束通告, 否则可能会引起死锁
通告可以是群体性的,也未必就是通告结束, 可以是任何需要表达的事件
package main
import (
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ready := make(chan struct{})
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
println(id, ":ready.") // 运动员准备就绪
<-ready // 等待发令枪响
println(id, ": running...")
}(i)
}
time.Sleep(time.Second)
println("ready? GO!")
close(ready) // 发令枪: 砰!
wg.Wait()
}
一次性事件用close效率更好, 没有多余开销, 连续或多样性事件, 可传递不同数据标志实现. 还可使用
sync.Cond
实现单播或广播事件.
对于closed或nil通道, 发送和接收操作都有相应规则:
- 向已关闭的通道发送数据,会引发panic
- 从已关闭的通道接收数据, 返回已缓冲数据或零值
- 无论收发, nil通道都会阻塞
package main
func main() {
c := make(chan int, 3)
c <- 10
c <- 20
close(c)
for i := 0; i < cap(c)+1; i++ {
x, ok := <-c
println(i, ":", ok, x)
}
}
// 0 : true 10
// 1 : true 20
// 2 : false 0
// 3 : false 0
重复关闭,或关闭nil通道都会引发panic发错误 ,
通道是默认双向的,并不区分发送和接收端,但某些时候,我们可以限制收发操作的方向, 来获得更严谨的操作逻辑
package main
import (
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
c := make(chan int)
var send chan<- int = c
var recv <-chan int = c
go func() {
defer wg.Done()
for x := range recv {
println(x)
}
}()
go func() {
defer wg.Done()
defer close(c)
for i := 0; i < 3; i++ {
send <- i
}
}()
wg.Wait()
}
如果要同时处理多个通道 , 可选用select语句.它会随机选择一个可用通道做收发操作.
import (
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
a, b := make(chan int), make(chan int)
// 接收端
go func() {
defer wg.Done()
for {
var (
name string
x int
ok bool
)
select { // 随机选择可用channel接收数据
case x, ok = <-a:
name = "a"
case x, ok = <-b:
name = "b"
}
if !ok { // 如果任一通道关闭, 则终止接收
return
}
println(name, x) // 输出接收到数据
}
}()
// 发送端
go func() {
defer wg.Done()
defer close(a)
defer close(b)
for i := 0; i < 10; i++ {
select { // 随机选择发送端
case a <- i:
case b <- i * 10:
}
}
}()
wg.Wait()
}
通常使用工厂方法将 goroutine和通道绑定.
package main
import "sync"
type receiver struct {
sync.WaitGroup
data chan int
}
func newReceiver() *receiver {
r := &receiver{
data: make(chan int),
}
r.Add(1)
go func() {
defer r.Done()
for x := range r.data { //接收消息,直到通道关闭
println("recv:", x)
}
}()
return r
}
func main() {
r := newReceiver()
r.data <- 1
r.data <- 2
close(r.data) // 关闭通道, 发出结束通知
r.Wait() // 等待接收者处理结束
}
// recv: 1
// recv: 2