golang中的channel是什么?如何使用channel?channel的原理是什么?

目录

介绍:

一、通道的定义和使用方法

1.1 通道的定义

1.2 通道的使用

1.3 通道的关闭

二、通道的底层实现

2.1 通道的数据结构

2.2 通道的发送和接收操作

2.3 通道的缓冲区

2.4 通道的等待队列

2.5 通道的关闭

2.6 通道的选择器

三、通道的使用技巧

3.1 使用无缓冲区通道进行同步

3.2 使用有缓冲区通道进行异步通信

3.3 使用 select 语句实现超时操作

3.4 使用 select 语句实现通道关闭操作

四、并发

4.1 goroutine

4.2 channel

4.3 channel 的使用

4.3.1 创建 channel

4.3.2 发送和接收数据

4.3.3 关闭 channel

4.3.4 select 语句

4.4 channel 的使用技巧

4.4.1 单向 channel

4.4.2 channel 的超时处理

4.4.3 多路复用

4.4.4 使用缓冲 channel

4.4.5 使用 sync.WaitGroup 等待 goroutine 结束

总结


介绍:

通道是 Go 编程语言的一个基本特性,它提供了协程之间通信和同步的方式。协程是轻量级线程,允许并发执行代码。通道用于在协程之间传递数据和控制流。本文将探讨通道的使用方法、通道的底层实现以及如何使用通道进行高效的并发编程。

一、通道的定义和使用方法

1.1 通道的定义

通道是一种类型,类似于一个队列。通过使用通道,我们可以在协程之间传递数据,协程会在通道上阻塞等待数据的到来,直到数据被发送到通道中,协程才会继续执行。

在 Go 中,使用 make() 函数来创建通道,语法如下:

ch := make(chan 数据类型)

其中,数据类型指定了通道可以传递的数据类型。通道可以传递任意类型的数据,包括自定义类型。

1.2 通道的使用

通道的基本操作包括发送和接收。发送操作使用 <- 运算符,接收操作使用 <- 运算符。例如:

ch := make(chan int)
ch <- 10 // 向通道 ch 发送整数 10
x := <-ch // 从通道 ch 接收一个整数,并赋值给变量 x

通道的发送和接收操作都是阻塞的。如果发送操作阻塞了,那么协程会一直阻塞,直到有另外一个协程从该通道中接收了数据。同样地,如果接收操作阻塞了,那么协程会一直阻塞,直到有另外一个协程向该通道中发送了数据。

为了防止通道死锁,我们通常需要使用协程来发送和接收数据,以便程序可以继续执行其他操作。

ch := make(chan int)
go func() {
    ch <- 10
}()
x := <-ch

在上面的示例中,我们使用了一个匿名协程来发送数据,然后在主协程中接收数据。

1.3 通道的关闭

通道可以通过调用 close() 函数来关闭。关闭通道后,所有的发送操作都会失败,并且接收操作会在通道中的数据被读取完毕后自动返回零值。例如:

ch := make(chan int)
go func() {
    ch <- 10
    ch <- 20
    ch <- 30
    close(ch)
}()
for x := range ch {
    fmt.Println(x)
}

在上面的示例中,我们使用了一个匿名协程向通道中发送了三个整数,并在发送完毕后关闭了通道。然后我们使用 for 循环来从通道中接收数据,直到通道被关闭。

二、通道的底层实现

2.1 通道的数据结构

通道是一个结构体类型,包含了一个指向数据队列的指针,以及发送和接收操作的相关信息。例如:

type hchan struct {
    qcount   uint           // 数据队列中的元素数量
    dataqsiz uint           // 数据队列的大小
    buf      unsafe.Pointer // 指向数据队列的指针
    elemsize uint16         // 每个元素的大小
    closed   uint32         // 通道是否已经关闭
    recvx    uint64         // 下一个接收操作的序列号
    sendx    uint64         // 下一个发送操作的序列号
    recvq    waitq          // 等待接收的协程队列
    sendq    waitq          // 等待发送的协程队列
    lock     mutex          // 互斥锁
}

2.2 通道的发送和接收操作

通道的发送和接收操作是通过一个序列号来实现的,每个发送和接收操作都有一个唯一的序列号。序列号由两部分组成:通道的序列号和操作类型(发送或接收)。

通道的序列号用于区分不同的通道,操作类型用于区分不同的发送和接收操作。发送操作的序列号是 sendx,接收操作的序列号是 recvx。

在发送操作中,发送的数据会被复制到通道的数据队列中,并更新 sendx 的值。如果数据队列已满,则发送操作会被阻塞,直到有其他协程从该通道中读取数据。

在接收操作中,接收到的数据会从通道的数据队列中读取,并更新 recvx 的值。如果数据队列为空,则接收操作会被阻塞,直到有其他协程向该通道中发送数据。

2.3 通道的缓冲区

通道可以具有缓冲区,缓冲区用于存储已发送但未被接收的数据。缓冲区的大小在创建通道时指定,可以通过通道的 dataqsiz 字段来访问。

在带有缓冲区的通道中,发送操作不会被阻塞,除非缓冲区已满。当缓冲区已满时,发送操作将被阻塞,直到有其他协程从该通道中读取数据。接收操作与非缓冲区通道中的操作相同,当缓冲区为空时会被阻塞。

2.4 通道的等待队列

每个通道都有一个等待接收的协程队列和一个等待发送的协程队列。在发送和接收操作被阻塞时,协程将被添加到相应的等待队列中。当数据可以被发送或接收时,等待队列中的协程会被唤醒并执行。

等待队列使用 waitq 结构体来实现,如下所示:

type waitq struct {
    first *sudog // 队列的第一个元素
    last  *sudog // 队列的最后一个元素
}

等待队列中的每个协程都会包含一个 sudog 结构体,该结构体用于存储协程的状态和等待的通道。sudog 结构体的定义如下:

type sudog struct {
    g           *g
    selectdone  uint32
    next        *sudog
    prev        *sudog
    elem        unsafe.Pointer
    waitlink    *sudog
    selectdonep *uint32
}

2.5 通道的关闭

通道可以通过调用 close 函数来关闭。关闭通道后,所有的发送操作都将被阻塞,并且所有未接收的数据都将被丢弃。对于已经关闭的通道,任何发送操作都将导致 panic 错误。

通道的关闭标志位保存在 closed 字段中,该字段是一个 uint32 类型的原子变量。当通道被关闭时,将 closed 置为 1,以指示通道已经关闭。

在发送操作中,如果通道已经关闭,则会返回 panic 错误。在接收操作中,如果通道已经关闭且数据队列为空,则接收操作将返回一个零值,并返回一个布尔值,以指示通道是否已经关闭。

2.6 通道的选择器

通道选择器是一种用于在多个通道之间进行选择的机制。通道选择器通过 select 语句实现,select 语句可以同时监听多个通道的读写操作,并在其中一个操作就绪时执行相应的代码块。

select 语句的语法如下:

select {
case <- ch1:
    // 处理 ch1 的接收操作
case ch2 <- value:
    // 处理 ch2 的发送操作
default:
    // 如果没有通道就绪,则执行默认操作
}

在 select 语句中,每个 case 语句都表示一个通道的读或写操作。在多个 case 语句中,只有一个可以被执行,具体选择哪个 case 取决于哪个操作最先就绪。如果没有通道就绪,则执行 default 语句块。

select 语句在底层使用了通道的等待队列,用于监听多个通道的读写操作。当其中一个操作就绪时,select 语句会从等待队列中找到对应的 sudog 结构体,并将对应的协程唤醒并执行。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端筱悦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值