type hchan struct {
qcount uint // 用于记录当前通道队列中已有的数据元素数量
dataqsiz uint // 指定了通道的循环队列(缓冲区)的大小。也就是通道最多能容纳的数据元素数量
buf unsafe.Pointer // 指向一个数组,该数组的大小由dataqsiz确定,用于存储通道中的数据元素
elemsize uint16 // 通道中每个数据元素的大小(以字节为单位)
closed uint32 // 用于标记通道是否已经关闭。当该字段的值为1时,表示通道已经关闭:否则,通道处于正常的开放状态
elemtype *_type // 用于标识通道中所传输数据元素的类型,_type是Go语言内部用于表示类型信息的一种结构体
sendx uint // 在有缓冲通道情况下,用于记录下一次发送数据时应该放入缓冲区的索引位置
recvx uint // 在有缓冲通道情况下,用于记录下一次接收数据时应该从缓冲区中取出数据的索引位置
recvq waitq // 一个等待队列,用于存放那些正在等待从该通道接收数据的协程(Goroutine)。等待读消息的goroutine队列
sendq waitq // 一个等待队列,不过它用于存放那些正在等待向该通道发送数据的协程。等待写消息的goroutine队列
lock mutex // 一个互斥锁。它用于保护hchn结构体中的所有字段,确保在多协程环境下对通道的各种操作(如发送、接收、查询通道状态等)能够安全、有序地进行。在对通道进行任何操作时,都需要先获取这个锁,操作完成后再释放锁,以避免并发冲突导致的数据不一致或其他异常情况
}
在 Golang 中,有缓冲的 channel 是一种可以存储固定数量元素的通道。
// 创建一个容量为n的有缓冲channel
ch := make(chan Type, n)
其中 n
表示缓冲区大小,即可以存储的元素数量。
当向有缓冲 channel 发送数据时,流程如下:
检查接收队列:首先检查是否有 goroutine 在等待接收数据
检查缓冲区:检查缓冲区是否已满
阻塞等待:发送 goroutine 被阻塞,加入发送等待队列,直到出现以下情况之一:
当从有缓冲 channel 接收数据时,流程如下:
检查发送队列:首先检查是否有 goroutine 在等待发送数据
检查缓冲区:检查缓冲区是否为空
检查 channel 状态:检查 channel 是否已关闭
v, ok := <-ch
形式接收)阻塞等待:接收 goroutine 被阻塞,加入接收等待队列,直到有数据可接收或 channel 被关闭
当 channel 被关闭时:
package main
import (
"fmt"
"time"
)
func main() {
// 创建容量为3的缓冲channel
ch := make(chan int, 3)
// 发送数据,不会阻塞,因为缓冲区有空间
ch <- 1
ch <- 2
ch <- 3
fmt.Println("成功发送3个数据到缓冲区")
// 启动一个goroutine在1秒后接收数据
go func() {
time.Sleep(1 * time.Second)
fmt.Println("开始接收数据")
fmt.Println(<-ch) // 输出1
fmt.Println(<-ch) // 输出2
fmt.Println("接收了2个数据")
}()
// 尝试发送第4个数据,此时缓冲区已满,会阻塞
// 直到上面的goroutine接收了数据,缓冲区有空间
ch <- 4
fmt.Println("成功发送第4个数据")
// 关闭channel
close(ch)
// 从已关闭但有数据的channel接收数据
fmt.Println(<-ch) // 输出3
fmt.Println(<-ch) // 输出4
// 从已关闭且无数据的channel接收数据
val, ok := <-ch
fmt.Printf("值: %d, 是否成功: %t\n", val, ok) // 值: 0, 是否成功: false
time.Sleep(2 * time.Second) // 等待goroutine完成
}
无缓冲的 channel(也称为同步 channel)是 Golang 中一种特殊的通道,它没有存储容量。无缓冲 channel 的一个重要特性是:发送和接收操作必须同时准备好才能完成,否则会阻塞。
// 创建一个无缓冲channel
ch := make(chan Type)
// 或者显式指定缓冲区大小为0
ch := make(chan Type, 0)
当向无缓冲 channel 发送数据时,流程如下:
检查接收队列:首先检查是否有 goroutine 在等待接收数据
阻塞等待:由于无缓冲 channel 没有存储空间,发送 goroutine 会被阻塞并加入发送等待队列,直到:
当从无缓冲 channel 接收数据时,流程如下:
检查发送队列:首先检查是否有 goroutine 在等待发送数据
检查 channel 状态:检查 channel 是否已关闭
v, ok := <-ch
形式接收)阻塞等待:接收 goroutine 被阻塞并加入接收等待队列,直到:
同步性:
阻塞条件:
用途:
package main
import (
"fmt"
"time"
)
func main() {
// 创建无缓冲channel
ch := make(chan int)
// 启动接收者goroutine
go func() {
fmt.Println("接收者:准备接收数据")
time.Sleep(2 * time.Second) // 模拟接收者延迟准备
val := <-ch
fmt.Printf("接收者:接收到数据 %d\n", val)
}()
// 主goroutine作为发送者
fmt.Println("发送者:准备发送数据")
fmt.Println("发送者:尝试发送数据,将被阻塞直到有接收者准备好")
ch <- 42 // 这里会阻塞,直到接收者准备好
fmt.Println("发送者:数据已发送")
// 演示同步通信
done := make(chan bool)
go func() {
fmt.Println("工作goroutine:开始执行任务")
time.Sleep(1 * time.Second)
fmt.Println("工作goroutine:任务完成")
done <- true // 通知主goroutine任务已完成
}()
// 等待工作完成
<-done // 阻塞直到接收到完成信号
fmt.Println("主goroutine:收到完成信号,程序结束")
}
锁机制:channel 内部使用互斥锁保护其数据结构
等待队列:无缓冲 channel 维护两个等待队列
直接传递:无缓冲 channel 中的数据传递是直接从发送者到接收者,不经过中间存储
公平性:等待队列通常采用 FIFO(先进先出)策略,确保公平性
如果您喜欢我的文章,请点击下面按钮随意打赏,您的支持是我最大的动力。
最新评论