从底层实现上来看,通道只是一个队列。同步模式下,发送和接收双方配对,然后直接复制数据给对方。如配对失败,则置入等待队列,直到另一方出现后才会唤醒。异步模式抢夺的则是数据缓冲槽。发送方要求有空槽可供写入,而接收方则要求有缓冲数据可读。需求不符时,同样加入等待队列,直到有另一方写入数据或腾出空槽后被唤醒。
内置函数 cap 和 len 返回缓冲区大小和当前已缓冲数量;而对于同步通道则都返回0,据此可判断通道是同步还是异步。
package main
import "fmt"
func main() {
a, b := make(chan int), make(chan int, 3)
b <- 1
b <- 2
fmt.Println(cap(a), len(a))
fmt.Println(cap(b), len(b))
}
输出:
0 0
3 2
对于 closed 或 nil 通道,发送和接收操作都有相应规则:
- 向已关闭通道发送数据,引发 panic。
- 从已关闭通道接收数据,返回已换从数据或零值。
- 无论收发,nil 通道都会阻塞。
单向通道
尽管可用 make 创建单向通道,但那没有任何意义。通常使用类型转换来获取单向通道,并分别赋予操作双方。
package main
import (
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
c := make(chan int)
var send chan<- int = c
var rece <-chan int = c
go func() {
defer wg.Done()
for x := range rece {
println(x)
}
}()
go func() {
defer wg.Done()
defer close(c)
for i := 0; i < 3; i++ {
send <- i
}
}()
wg.Wait()
}
输出为:
0
1
2
同样, close 不能用于接收端。
package main
func main() {
c := make(chan int, 2)
var rece <-chan int = c
close(rece) // 错误:invalid operation: close(rece) (cannot close receive-only channel)
}
选择
如果要同时处理多个通道,可选用 select 语句。它会随机选择一个可用通道做收发操作。
package main
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 {
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 ++ { // 随机发送 chan
select {
case a <- i:
case b <- i * 10:
}
}
}()
wg.Wait()
}
输出:
b 0
a 1
b 20
a 3
b 40
a 5
b 60
b 70
b 80
b 90
如果要等全部通道消息处理结束,可将已完成通道设置为 nil。这样它就会被阻塞,不再被 select 选中。
package main
import "sync"
func main() {
var wg sync.WaitGroup
wg.Add(3)
a, b := make(chan int), make(chan int)
go func() {
defer wg.Done()
for {
select {
case x, ok := <-a:
if !ok { // 如果通道关闭,则设置为 nil, 阻塞
a = nil
break
}
println("a", x)
case x, ok := <- b:
if !ok {
b = nil
break
}
println("b", x)
}
if a == nil && b == nil {
return
}
}
}()
go func() { // 发送端 a
defer wg.Done()
defer close(a)
for i := 0; i < 3; i++ {
a <- i
}
}()
go func() { // 发送端 b
defer wg.Done()
defer close(b)
for i := 0; i < 5; i++ {
b <- i * 10
}
}()
wg.Wait()
}
输出为:
a 0
b 0
a 1
b 10
a 2
b 20
b 30
b 40
同步
将 Mutex 作为匿名字段时,相关方法必须实现为 pointer-receiver,否则会因复制导致锁机制失效。
package main
import (
"sync"
"time"
)
type data struct {
sync.Mutex
}
func (d data)test(s string) {
d.Lock()
defer d.Unlock()
for i := 0; i < 5; i++ {
println(s, i)
time.Sleep(time.Second)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
var d data
go func() {
defer wg.Done()
d.test("read")
}()
go func() {
defer wg.Done()
d.test("write")
}()
wg.Wait()
}
输出为:
write 0
read 0
write 1
read 1
write 2
read 2
write 3
read 3
read 4
write 4
锁失效,将 receiver 类型改为 *data 后正常。也可用嵌入 *Mutex 来避免复制问题,但那需要专门初始化。
Mutex 不支持递归锁,即便在同一 goroutine 下也会导致死锁。
package main
import "sync"
func main() {
var m sync.Mutex
m.Lock()
{
m.Lock()
m.Unlock()
}
m.Unlock()
}
输出:
fatal error: all goroutines are asleep - deadlock!
相关建议:
- 对性能要求较高时,应避免使用 defer Unlock。
- 读写并发时,用 RWMutex 性能会更好一些。
- 对单个数据读写保护,可尝试用原子操作。
- 执行严格测试,尽可能打开数据竞争检查。