深入理解golang channel

channel详解

CSP模型

go语言的并发是基于CSP并发模型实现的。

golang的并发哲学:Do not communicate by sharing memory; instead, share memory by communicating。

channel主要用于在多个goroutine之间进行通信。

channel是并发安全的。

channel的使用方法:

var ch chan int // 声明一个int类型的channel,不进行初始化
var ch = make(chan int) // 创建一个无缓冲的channel
var ch = make(chan int,1) // 创建一个有缓冲的channel
var ch = make(<-chan int,1) // 创建一个只读的单向channel
var ch = make(chan<- int,1) // 创建一个只写的单向channel
ch <- 1 // 向channel中发送一个值
x,ok := <- ch // 从channel中接收一个值,如果channel关闭,则ok返回false,通过判断ok的值可以判断channel是否已经关闭
close(ch) // 关闭channel
for item := range ch {} // 从channel中读取值,如果channel关闭,读取完值后会退出循环,否则会阻塞

channel使用原则:

  1. 无缓冲的channel是同步的,有缓冲的channel是异步的
  2. 对未初始化的channel进行关闭操作,会引发panic
  3. 对未初始化的channel进行发送或接收数据,会造成阻塞
  4. 向无缓冲的channel发送或接收数据会发送阻塞
  5. 向有缓冲的channel发送数据,如果channel容量已满,会发生阻塞
  6. 从有缓冲的channel接收数据,如果channel已经为空且channel未关闭,会发送阻塞
  7. 从有缓冲的channel接收数据,如果channel已经为空且channel已经关闭,会读取到数据类型的零值
  8. 向已经关闭的channel发送数据,会引发panic
  9. 关闭一个已经关闭的channel会导致panic

channel的底层实现

channel相关实现的源码位于:src/runtime/chan.go

  • hchan
type hchan struct {
	qcount   uint           // 当前队列的长度,len()的结果
	dataqsiz uint           // 循环队列大小,cap()的结果
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16		// 元素大小
	closed   uint32		// 是否关闭
	elemtype *_type // 元素类型
	sendx    uint   // 环形队列下一次发送元素的索引
	recvx    uint   // 换循队列下一次接收元素的索引
	recvq    waitq  // 接收的goroutine等待队列
	sendq    waitq  // 发送的goroutine等待队列

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex	// 互斥锁,保证同步安全
}

type waitq struct {
	first *sudog
	last  *sudog
}

hchan为一个环形队列,waitq链表表示被阻塞的goroutine的等待队列。

  • makechan 为环形队列申请内存

  • chansend

    1. 获取同步锁,对缓冲队列进行加锁
    2. 如果有等待的goroutinue,那么数据直接发送给这个接收goroutine,数据拷贝1次
    3. 如果缓冲队列没有满,将发送的数据拷贝到队列中,sendx加1
    4. 如果缓冲队列满了,那么将发送goroutine放到发送阻塞队列中
  • chanrecv

    1. 对于队列进行加锁
    2. 如果有send阻塞队列,那么
      • 如果channel没有缓冲区,那么直接从send goroutine接收数据
      • 否则从缓冲区队列头取出数据,并且唤醒send开始写入数据到队列尾部
    3. 缓存队列不为空,那么从队列获取数据,数据都是拷贝出来的。
    4. 缓存队列为空,那么将recv goroutine加入到recv阻塞队列中
  • closechan

    1. 将ch.closed设置为1

    2. 唤醒 recvq 队列里面的阻塞 goroutine

    3. 唤醒 sendq 队列里面的阻塞 goroutine

      处理方式是分别遍历 recvq 和 sendq 队列,将所有的 goroutine 放到 glist 队列中,最后唤醒 glist 队列中的 goroutine.

select

select的调度方法为selectgo,源码位于src\runtime\select.go 文件

最终会调用chan.go中的selectrecv、selectsend等方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值