【Golang】源码学习:runtime/chan.go:了解channel背后的实现原理

本文详细探讨了Go语言中channel的实现原理,从hchan结构和初始化开始,分析了无阻塞通信、发送方和接收方的阻塞情况,以及channel的关闭、空值和其他细节。通过源码学习,揭示了channel如何实现协程间的安全通信,强调了Go的CSP并发模型。文章还提到了channel在并发编程中的重要性以及其在内存管理和并发安全性上的独特设计。
摘要由CSDN通过智能技术生成

一、概述

Go通过channel在独立工作的Goroutine之间实现了通信,其背后是Go的CSP并发模型,其理念被概括为:

Do not communicate by sharing memory; instead, share memory by communicating.

不要通过共享内存的方式来通信,相反,要通过通信来共享内存。

 这一策略在Go中的实现,就是将channel即管道作为一等公民,就像现实中的“管道”一样沟通独立的协程,发送方协程将信息放入管道,由接受方协程从管道中取出,以此来达到通信的目的,这种模式与Linux中的管道有些类似,主要特点有:

  • 协程间安全(goroutine-safe)
  • 跨协程存储和传输信息(store and pass values between goroutines)
  • 提供FIFO机制(provide FIFO semantics)
  • 具备阻塞和恢复协程的能力(can cause goroutines to block and unblock)

在实际使用中,channel在并发场景下发挥了巨大的作用,是GoLang并发编程的支柱之一,而其背后的实现机制是什么样、Go团队是如何通过700多行代码实现了如此强大的功能,将是本文要探究的主要内容。

本文主要学习了有关协程调度和通道机制的热门文章,结合源码和大佬的PPThttps://speakerdeck.com/kavya719/understanding-channels (需要科学上网),分析和学习Go channel的实现机制。

二、hchan的结构和初始化

结构体hchan描述了一个管道的主要组成结构,代码如下:

type hchan struct {

        //对于有缓冲的channel,hchan维护一个缓冲区来储存缓冲内容
	qcount   uint           // channel已缓存数据数量
	dataqsiz uint           // channel容量
	buf      unsafe.Pointer // 带缓冲的channel的缓冲区,环形数组
	sendx    uint   // 缓冲区中待发送位置索引
	recvx    uint   // 缓冲区中待接收位置索引

        //channel存储元素相关信息
	elemsize uint16         // 存储元素大小
	closed   uint32         //是否被关闭标记
	elemtype *_type         // 储存元素类型

        //储存channel连接两端Goroutine的抽象信息
	recvq    waitq  // 接收者信息队列(双向链表)
	sendq    waitq  // 发送者信息队列(双向链表)

        //对channel中上述所有域的保护锁
	lock mutex
}

可以看到,除互斥锁外,一个hchan结构主要包含了三大部分:

  1. 带缓冲区管道特有的一个环形缓冲区及其维护信息。
  2. 关于本管道目标传输数据的类型信息。
  3. 用于阻塞和恢复管道两端Goroutine的两个队列。

使用make语句初始化一个channel时,将在当前程序堆上为一个hchan申请空间,并返回指向这个空间的指针,因此,一个channel在被创建时,就已经已指针的形势存在,不需要再用指向channel的指针来传递channel

对于带缓冲管道,势必需要包含一个缓冲区来完成数据缓冲工作,示意如下:

 通过hchan的主要结构可知,在初始化hchan时,主要完成了两大工作:

1、分情况初始化缓冲区buf相关参数

	switch {
	case mem == 0:
                //不带有缓冲区,直接初始化,将不为buf分配内存
		c = (*hchan)(mallocgc(hchanSize, nil, true))
		c.buf = c.raceaddr()
	case elem.ptrdata == 0:
                //传输非指针类型的带缓冲通道,将buf指向分配内存的首地址
		c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
		c.buf = add(unsafe.Pointer(c), hchanSize)
	default:
		// 处理带缓冲且元素类型为指针的情况
		c = new(hchan)
		c.buf = mallocgc(mem, elem, true)
	}

2、保存channel传输元素的类型信息

	c.elemsize = uint16(elem.size)
	c.elemtype = elem
	c.dataqsiz = uint(size)

三、无阻塞情况下的简单通信

当channel带有缓冲区且缓冲区未满/未空时,将不存在阻塞的情况,发送方将数据拷贝到hcan.buf中当前sendx索引的位置,接收方直接从hchan.buf中取出当前recvx索引位置的数据。

当然,对hchan内部字段的操作都应该在互斥锁的并发保护下进行。

1、发送方操作:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值