返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

9.2 通道

发布于 2024-10-12 19:15:49 字数 3292 浏览 0 评论 0 收藏 0

鼓励使用 CSP 通道,以通信来代替内存共享,实现并发安全。

通道(channel)行为类似消息队列。不限收发人数,不可重复消费。

Don't communicate by sharing memory, share memory by communicating.

CSP: Communicating Sequential Process.

同步

没有数据缓冲区,须收发双方到场直接交换数据。

  • 阻塞,直到有另一方准备妥当或通道关闭。
  • 可通过 cap == 0 判断为无缓冲通道。
func main() {
	quit := make(chan struct{})
	data := make(chan int)

	go func() {
		data <- 11
	}()

	go func() {
		defer close(quit)
		
		println(<- data)
		println(<- data)
	}()

	data <- 22
	<- quit
}

异步

通道自带固定大小缓冲区。有数据或有空位时,不会阻塞。

  • caplen 获取缓冲区大小和当前缓冲数。
func main() {
	quit := make(chan struct{})
	data := make(chan int, 3)

	data <- 11
	data <- 22
	data <- 33
    
    println(cap(data), len(data))  // 3 3

	go func() {
		defer close(quit)

		println(<- data)
		println(<- data)
		println(<- data)

		println(<- data)  // block
	}()

	data <- 44
	<- quit
}

缓冲区大小仅是内部属性,不属于类型组成部分。

通道变量本身就是指针,可判断是否为同一对象或 nil

func main() {
	var a, b chan int = make(chan int, 3), make(chan int)
	var c chan bool
    
	println(a == b)               // false
	println(c == nil)             // true
	println(a, unsafe.Sizeof(a))  // 0xc..., 8
}

关闭

对于 closednil 通道,规则如下:

  • 无论收发, nil 通道都会阻塞。
  • 不能关闭 nil 通道。
  • 重复关闭通道,引发 panic
  • 向已关闭通道发送数据,引发 panic
  • 从已关闭通道接收数据,返回缓冲数据或零值。

提示, nil 通道是指没有 make 的变量。

鉴于通道关闭后,所有基于此的阻塞都被解除,可用作通知。

没有判断通道是否已被关闭的直接方法,只能透过收发模式获知。

func main() {
	c := make(chan int)
	close(c)

	// close(c)
	// ~~~~~ panic: close of closed channel
    
    // 不会阻塞,返回零值。
    
	println(<- c) // 0
	println(<- c) // 0
}
func main() {
	c := make(chan int, 2)
	c <- 1
	close(c)	

    // 不会阻塞,返回零值。
    
	println(<- c)  // 1
	println(<- c)  // 0
	println(<- c)  // 0
}

为避免重复关闭,可包装 close 函数。

也可以类似方式封装 sendrecv 操作。

func closechan[T any](c chan T) {
	defer func(){
		recover()
	}()

	close(c)
}

func main() {
	c := make(chan int, 2)

	closechan(c)
	closechan(c)
}

保留关闭状态。注意,为并发安全,关闭和获取关闭状态应保持同步。

可使用 sync.RWMutexsync.Once 优化设计。

type Queue[T any] struct {
	sync.Mutex	

	ch     chan T
	cap    int
	closed bool
}

func NewQueue[T any](cap int) *Queue[T] {
	return &Queue[T]{
		ch: make(chan T, cap),
	}
}

func (q *Queue[T]) Close() {
	q.Lock()
	defer q.Unlock()

	if !q.closed {
		close(q.ch)
		q.closed = true
	}
}

func (q *Queue[T]) IsClosed() bool {
	q.Lock()
	defer q.Unlock()

	return q.closed
}

// ---------------------------------

func main() {
	var wg sync.WaitGroup
	q := NewQueue[int](3)

	for i := 0; i < 10; i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()
			defer q.Close()
			println(q.IsClosed())
		}()
	}

	wg.Wait()
}

利用 nil 通道阻止退出。

func main() {
    <-(chan struct{})(nil)    // select{}
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文