Channel概念
Channel管道 是Go语言中一种强大的通信机制,它使得在并发运行的goroutines之间可以高效地交换数据。通过Channel管道,goroutines能够:
- 同步执行:Channel管道可以用来协调并发任务的执行顺序,确保它们在正确的时间点上执行。
- 数据共享:与传统的共享内存模型不同,Channel管道提供了一种通过通信来共享数据的方式,这有助于避免竞态条件和锁的复杂性。
- 消息队列:Channel管道可以作为消息队列使用,允许goroutines发送和接收消息,实现生产者-消费者模式。
- 线程同步:Channel管道还可以作为线程同步的工具,用于控制线程间的协作和通信。
Channel使用
创建
msgChan:=make(chan int)
masgChan:=make(chan int,100)
chan分为有缓冲区,无缓冲区
有缓冲区:可以向里面写数据,也可以读数据,只有缓冲区满了写操作才会阻塞,当缓冲空了读操作才会阻塞
无缓冲区:只有读操作就绪才能写入数据,否则都是阻塞着的,实现同步。也就是说,发送的数据需要被读取后,发送才会完成
读写操作
使用“< -”操作符,变量位置不同表示不同操作
//向chan中写数据
msgChan<-i
//读取chan中的shuj
i:=<-MsgChan
操作符“<-”标识了数据的流向,处于箭头指向的一方可以看作数据的流入方;反之,则为数据流出方。当channel为流入方时,代表向channel中写入数据;反之,则代表从channel中读取数据。
channel实现原理
Chanel的数据结构是hchan
type hchan struct {
qcount uint // 当前通道中的元素个数
dataqsiz uint // 缓冲区大小
buf unsafe.Pointer // 指向存储通道数据的缓冲区的指针,循环队列
elemsize uint16 // 元素大小
closed uint32 // 标志通道是否已关闭
elemtype *_type // 元素类型
sendx uint // 发送元素在环形缓冲区索引,用于跟踪循环队列中的位置。
recvx uint // 接收元素在环形缓冲区索引,
recvq waitq // 接收等待队列
sendq waitq // 发送等待队列
lock mutex // 通道的锁
}
buf用来存储,dataqsiz表示缓冲区大小,当一个协程向channel写入数据,如果未有足够的空间吧容纳,会被加入sendq队列中,被阻塞的接收数据的协程会加入recvq队列
type waitq struct {
first *sudog //队列头部
last *sudog //队列尾部
}
写数据的过程
- 数据准备:在发送方的协程中准备要发送的数据
- 首先检查recvq有没有正在等待读取的协程。如果有,就代表没有缓冲区或者缓冲区中没有数据,那么,就从recvq中拿出第一个协程,数据交由该协程处理,然后激活该协程
- 如果recvq没有阻塞的协程,则尝试将数据写入缓冲区
- 如果没有缓冲区,或者缓冲区已满,则阻塞该协程,并将协程对象加入sendq中
读取数据的过程
- 首先检查sendq是否为空,如果不为空,且没有缓冲区,那么就从sendq中获取第一个协程,并复制其数据,然后激活该协程、
- 如果sendq不为空,但有缓冲区,那么就从缓冲区中获取数据
- 如果sendq为空。同样要尝试从缓冲区中获取数据,如果不能读取数据,则将自己加入recvq中
整体
Chanel各种操作导致阻塞和协程泄露的场景
写操作
- 向nil通道发送数据会被阻塞
- 向无缓冲channel写数据,如果读协程没有准备好,会阻塞
- 无缓冲channel,必须要有读有写,有数据后必须读出来,否则导致channel阻塞,从而使协程阻塞导致协程泄露
- 向有缓冲的chanel写数据,如果缓冲已满,会阻塞
读操作
- 从nil通道接收数据会被阻塞
- 从无缓冲channel读取数据,如果写协程没有准备好,会被阻塞
- 从有缓冲channel读数据,如果缓冲为空会阻塞
向已关闭的Channel发送或者读取数据都会发生错误
协程泄露情况
-
协程未关闭channel
如果一个协程创建Channel但没有正确关闭,并且其他协程尝试从中读取或者写入数据,这都会导致死锁和协程泄露
-
协程等待永远不会发送的数据
如果一个协程正在Channel上等待数据,而发送数据的协程已经结束或者阻塞,这多可能导致等待的协程永远无法继续执行
-
错误的channel关闭
不恰当的时机关闭Channel,例如仍有协程等待数据时关闭Channel
-
协程在Channel上无限期阻塞
如果Channel的发送或接收操作没有适当的超时或退出机制,协程可能无限期阻塞在Channel上
-
协程未处理Channel关闭
在读取Channel时Channel已经关闭,但没有相应地退出循环或逻辑,这可能导致goroutine泄露。