Go 中的通道

从底层实现上来看,通道只是一个队列。同步模式下,发送和接收双方配对,然后直接复制数据给对方。如配对失败,则置入等待队列,直到另一方出现后才会唤醒。异步模式抢夺的则是数据缓冲槽。发送方要求有空槽可供写入,而接收方则要求有缓冲数据可读。需求不符时,同样加入等待队列,直到有另一方写入数据或腾出空槽后被唤醒。
内置函数 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 性能会更好一些。
  • 对单个数据读写保护,可尝试用原子操作。
  • 执行严格测试,尽可能打开数据竞争检查。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值