无缓冲的 channel 和有缓冲的 channel 的区别?
在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。
- 无缓冲的 channel
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个无缓冲的 channel
ch := make(chan int)
// 启动一个 goroutine 来接收数据
go func() {
// 接收数据之前会阻塞,直到 main goroutine 发送数据
val := <-ch
fmt.Println("接收到的数据:", val)
}()
// 模拟一些操作
time.Sleep(1 * time.Second)
// 发送数据到 channel,会阻塞直到接收方读取数据
ch <- 42
fmt.Println("数据已发送")
}
由于是无缓冲的 channel,main goroutine 在发送 42 时会阻塞,直到 goroutine 从 channel 中接收到这个值,程序才会继续执行。
2. 有缓冲的 channel
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个带有缓冲区大小为 2 的 channel
ch := make(chan int, 2)
// 发送两个数据到 channel
ch <- 1
fmt.Println("发送了数据 1")
ch <- 2
fmt.Println("发送了数据 2")
// 此时,由于缓冲区还有空间,发送不会阻塞
go func() {
// 延迟读取,模拟一些操作
time.Sleep(2 * time.Second)
val := <-ch
fmt.Println("接收到的数据:", val)
}()
// 继续发送数据
time.Sleep(1 * time.Second)
ch <- 3
fmt.Println("发送了数据 3")
// 接收数据
time.Sleep(2 * time.Second)
val := <-ch
fmt.Println("接收到的数据:", val)
}
这里的 channel 有缓冲区大小为 2,因此前两个 ch <- 操作不会阻塞,因为缓冲区有足够空间。第三次发送数据时,如果缓冲区已满,发送方会阻塞,直到接收方读取数据并释放空间。
channel和select底层数据结构是怎样的?
type hchan struct {
qcount uint // 通道中已经存在的数据个数
dataqsiz uint // 环形队列的大小
buf unsafe.Pointer // 环形队列的指针
elemsize uint16 // 每个元素的大小
closed uint32 // 通道是否关闭
sendx uint // 发送操作的索引
recvx uint // 接收操作的索引
recvq waitq // 等待接收的 Goroutine 队列
sendq waitq // 等待发送的 Goroutine 队列
}
recvq/sendq:表示接收和发送操作等待的 Goroutine 队列。当 select 语句中有对通道的接收或发送操作时,如果通道未就绪,当前 Goroutine 会被加入相应的等待队列。
type scase struct {
c *hchan // 指向通道的指针
kind uint16 // 操作类型(发送、接收)
pc uintptr // 程序计数器,用于跟踪执行位置
elem unsafe.Pointer // 数据元素的指针,用于发送或接收操作
}
c:指向通道的指针,表示这个 case 监听哪个通道。
kind:表示操作类型,是发送、接收还是默认 case。
elem:存储数据的指针,用于发送或接收操作时的存取。