当前位置:首页 > 科技  > 软件

深入Go原理:协程间通信基础Chan

来源: 责编: 时间:2024-05-29 08:53:54 266观看
导读在 Go 语言中,chan(通道)是用于在不同 goroutine 之间进行通信和同步的重要机制。它的设计和实现允许在并发编程中安全、有效地传递数据。以下是 chan 的工作原理和实现细节基本概念通道类型通道有类型,指定了通道能够传

在 Go 语言中,chan(通道)是用于在不同 goroutine 之间进行通信和同步的重要机制。它的设计和实现允许在并发编程中安全、有效地传递数据。以下是 chan 的工作原理和实现细节Leg28资讯网——每日最新资讯28at.com

基本概念

通道类型

通道有类型,指定了通道能够传递的数据类型。例如,chan int 是一个只能传递整数的通道。Leg28资讯网——每日最新资讯28at.com

无缓冲通道

没有缓冲区的通道,发送和接收操作是同步的,即发送操作会阻塞直到有接收操作发生。Leg28资讯网——每日最新资讯28at.com

有缓冲通道

具有一定缓冲区的通道,发送操作在缓冲区未满时不会阻塞,直到缓冲区满时才会阻塞。Leg28资讯网——每日最新资讯28at.com

通道的内部结构

通道在内部是通过 hchan 结构体来实现的。这个结构体包含了通道的基本信息和状态Leg28资讯网——每日最新资讯28at.com

type hchan struct {    qcount   uint           // 缓冲区中数据的数量    dataqsiz uint           // 缓冲区的大小    buf      unsafe.Pointer // 缓冲区指针    elemsize uint16         // 元素的大小    closed   uint32         // 通道是否关闭    sendx    uint           // 发送操作的索引    recvx    uint           // 接收操作的索引    recvq    waitq          // 等待接收的 goroutine 队列    sendq    waitq          // 等待发送的 goroutine 队列    lock     mutex          // 保护通道的互斥锁}

发送和接收操作

无缓冲通道

发送操作

如果没有接收者,发送方会阻塞,直到有接收方开始接收。Leg28资讯网——每日最新资讯28at.com

接收操作

如果没有发送者,接收方会阻塞,直到有发送方开始发送。Leg28资讯网——每日最新资讯28at.com

有缓冲通道

发送操作

如果缓冲区未满,数据直接写入缓冲区。若缓冲区已满,发送方会阻塞,直到有空间可用。Leg28资讯网——每日最新资讯28at.com

接收操作

如果缓冲区不为空,数据直接从缓冲区读取。若缓冲区为空,接收方会阻塞,直到有数据可读。Leg28资讯网——每日最新资讯28at.com

通道的同步机制

通道的发送和接收操作都是原子性的,并且由互斥锁保护。这确保了多个 goroutine 同时操作通道时不会发生竞态条件。Leg28资讯网——每日最新资讯28at.com

互斥锁(Mutex)

每个通道都有一个互斥锁,用于保护通道的状态和数据。Leg28资讯网——每日最新资讯28at.com

等待队列(Wait Queue)

通道维护两个等待队列,一个用于等待接收的 goroutine,一个用于等待发送的 goroutine。当发送或接收操作不能立即完成时,goroutine 会被加入相应的等待队列中。Leg28资讯网——每日最新资讯28at.com

通道关闭

关闭通道

通过调用 close(chan) 可以关闭通道。关闭操作会设置通道的 closed 标志,并唤醒所有在通道上阻塞的发送和接收操作。Leg28资讯网——每日最新资讯28at.com

关闭后的操作

向已关闭的通道发送数据会引发 panic,从已关闭的通道接收数据会立即返回零值。Leg28资讯网——每日最新资讯28at.com

实现细节

以下是通道发送和接收操作的一些实现细节Leg28资讯网——每日最新资讯28at.com

发送操作

chan send 检查通道是否关闭,如果没有接收者且缓冲区未满,数据会被直接写入缓冲区,否则会阻塞当前 goroutine 并将其加入 sendq。Leg28资讯网——每日最新资讯28at.com

接收操作

chan recv 检查通道是否关闭或缓冲区是否为空,如果有数据则直接返回,否则阻塞当前 goroutine 并将其加入 recvq。Leg28资讯网——每日最新资讯28at.com

总结

Go 语言中的通道通过上述机制实现了 goroutine 之间的安全、高效通信。通道的设计考虑了并发编程中的同步问题,通过缓冲机制和等待队列的管理,使得数据传递和同步操作都能高效地进行。Leg28资讯网——每日最新资讯28at.com

例子

在 Go 语言中,可以通过 make 函数来定义通道。根据是否指定缓冲区大小,可以创建无缓冲区通道和有缓冲区通道。以下是具体的定义和示例:Leg28资讯网——每日最新资讯28at.com

无缓冲区通道

无缓冲区通道是指在没有缓冲区的情况下,发送和接收操作是同步的。发送操作会一直阻塞,直到有接收者接收数据。Leg28资讯网——每日最新资讯28at.com

定义无缓冲区通道
ch := make(chan int)
示例
package mainimport (    "fmt")func main() {    ch := make(chan int)    // 启动一个 goroutine 发送数据    go func() {        ch <- 42 // 发送操作会阻塞,直到有接收者    }()    // 接收数据    value := <-ch    fmt.Println(value) // 输出: 42}

在这个例子中,ch 是一个无缓冲区通道,发送操作 ch <- 42 会阻塞,直到主 goroutine 执行 <-ch 接收数据。Leg28资讯网——每日最新资讯28at.com

有缓冲区通道

有缓冲区通道允许在缓冲区未满时发送操作不会阻塞,直到缓冲区满时才会阻塞。Leg28资讯网——每日最新资讯28at.com

定义有缓冲区通道
ch := make(chan int, 3) // 创建一个缓冲区大小为 3 的通道
示例
package mainimport (    "fmt")func main() {    ch := make(chan int, 3) // 定义缓冲区大小为 3 的通道    // 发送数据到通道,不会阻塞    ch <- 1    ch <- 2    ch <- 3    // 缓冲区已满,下面的发送操作会阻塞,直到有接收者    go func() {        ch <- 4    }()    // 接收数据    fmt.Println(<-ch) // 输出: 1    fmt.Println(<-ch) // 输出: 2    fmt.Println(<-ch) // 输出: 3    fmt.Println(<-ch) // 输出: 4}

在这个例子中,ch 是一个有缓冲区通道,缓冲区大小为 3。前 3 个发送操作不会阻塞,直到缓冲区满后,第 4 个发送操作会阻塞,直到有接收者开始接收数据。Leg28资讯网——每日最新资讯28at.com

总结

通过 make(chan T) 可以创建无缓冲区通道,通过 make(chan T, capacity) 可以创建有缓冲区通道。无缓冲区通道在发送和接收操作上是同步的,而有缓冲区通道允许在缓冲区未满时进行非阻塞的发送操作。通过以上示例,可以清晰地看到两种通道的行为差异。Leg28资讯网——每日最新资讯28at.com

select

在 Go 语言中,select 语句用于处理多个通道的通信操作。它的作用是让 goroutine 可以同时等待多个通道操作(发送或接收),并在其中任何一个通道操作完成时执行相应的分支代码。select 语句的使用使得在处理并发编程时更加灵活和高效。Leg28资讯网——每日最新资讯28at.com

select 语句的基本用法

select 语句的语法与 switch 语句类似,但它专门用于通道操作。每个 case 分支包含一个通道操作(发送或接收),select 会选择其中一个已准备好的通道操作进行处理。Leg28资讯网——每日最新资讯28at.com

语法结构

select {case expr1:    // 如果 expr1 通道操作可以进行,则执行此分支case expr2:    // 如果 expr2 通道操作可以进行,则执行此分支default:    // 如果没有任何通道操作可以进行,则执行此分支}

示例:使用 select 同时等待多个通道操作

以下是一个使用 select 语句的示例:Leg28资讯网——每日最新资讯28at.com

package mainimport (    "fmt"    "time")func main() {    ch1 := make(chan string)    ch2 := make(chan string)    // 启动第一个 goroutine    go func() {        time.Sleep(2 * time.Second)        ch1 <- "message from ch1"    }()    // 启动第二个 goroutine    go func() {        time.Sleep(1 * time.Second)        ch2 <- "message from ch2"    }()    for i := 0; i < 2; i++ {        select {        case msg1 := <-ch1:            fmt.Println(msg1)        case msg2 := <-ch2:            fmt.Println(msg2)        }    }}

在这个例子中,有两个通道 ch1 和 ch2,每个通道都在不同的 goroutine 中发送消息。select 语句使得主 goroutine 可以同时等待两个通道的消息,并在任意一个通道接收到消息时执行相应的分支。Leg28资讯网——每日最新资讯28at.com

default 分支

如果在 select 语句中添加了 default 分支,当所有通道操作都无法立即进行时,会执行 default 分支。这样可以避免 select 语句阻塞。Leg28资讯网——每日最新资讯28at.com

示例:带有 default 分支的 select

package mainimport (    "fmt"    "time")func main() {    ch := make(chan string)    go func() {        time.Sleep(2 * time.Second)        ch <- "message"    }()    for {        select {        case msg := <-ch:            fmt.Println(msg)            return        default:            fmt.Println("No message received, doing other work")            time.Sleep(500 * time.Millisecond)        }    }}

在这个例子中,如果通道 ch 上没有消息可接收,select 会执行 default 分支,打印一条消息并继续执行其他工作。Leg28资讯网——每日最新资讯28at.com

总结

select 语句是 Go 语言中处理并发编程的重要工具,通过它可以同时等待多个通道操作并在其中一个操作完成时进行相应处理。select 提供了一种灵活且高效的方式来处理多个通道之间的通信,使得并发程序的设计更加简洁和直观。Leg28资讯网——每日最新资讯28at.com

等待多个通道的逻辑

在 Go 语言的 select 语句中,如果有多个通道操作同时准备就绪(即都可以进行),Go 运行时会从这些通道操作中随机选择一个执行。一旦某个通道操作被选中并执行,其它通道的等待操作将不会继续进行。每次执行 select 语句时都会重新评估所有通道操作。Leg28资讯网——每日最新资讯28at.com

示例:多个通道同时就绪

为了更好地理解这个机制,以下是一个示例,展示当多个通道同时准备就绪时,select 语句的行为:Leg28资讯网——每日最新资讯28at.com

package mainimport (    "fmt"    "time")func main() {    ch1 := make(chan string)    ch2 := make(chan string)    ch3 := make(chan string)    go func() {        time.Sleep(1 * time.Second)        ch1 <- "message from ch1"    }()    go func() {        time.Sleep(1 * time.Second)        ch2 <- "message from ch2"    }()    go func() {        time.Sleep(1 * time.Second)        ch3 <- "message from ch3"    }()    for i := 0; i < 3; i++ {        select {        case msg1 := <-ch1:            fmt.Println(msg1)        case msg2 := <-ch2:            fmt.Println(msg2)        case msg3 := <-ch3:            fmt.Println(msg3)        }    }}

在这个示例中,有三个通道 ch1, ch2, 和 ch3,每个通道在 1 秒后发送一个消息。因为所有通道在同一时间准备就绪,select 语句将从中随机选择一个进行处理,并打印相应的消息。每次循环都会重新评估所有通道。Leg28资讯网——每日最新资讯28at.com

结论

当 select 语句等待多个通道时,如果其中一个通道操作可以进行,其它通道的操作不会继续等待,而是等待下一次 select 语句的评估。每次 select 语句执行时都会重新评估所有通道操作,并选择其中一个可以进行的操作。如果多个通道同时就绪,select 会随机选择其中一个进行处理。Leg28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-91354-0.html深入Go原理:协程间通信基础Chan

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: SpringBoot优雅定制接口参数格式转换

下一篇: SpringBoot的自动装配,你学会了吗?

标签:
  • 热门焦点
  • Find N3入网:最高支持16+1TB

    OPPO将于近期登场的Find N3折叠屏目前已经正式入网,型号为PHN110。本次Find N3在外观方面相比前两代有很大的变化,不再是小号的横向折叠屏,而是跟别的厂商一样采用了较为常见的
  • Redmi Buds 4开箱简评:才199还有降噪 可以无脑入

    在上个月举办的Redmi Note11T Pro系列新机发布会上,除了两款手机新品之外,Redmi还带来了两款TWS真无线蓝牙耳机产品,Redmi Buds 4和Redmi Buds 4 Pro,此前我们在Redmi Note11T
  • 六大权益!华为8月服务日开启:手机免费贴膜、维修免人工费

    8月5日消息,一年一度的华为开发者大会2023(Together)日前在松山湖拉开帷幕,与此同时,华为8月服务日也式开启,到店可享六大专属权益。华为用户可在华为商城Ap
  • 学习JavaScript的10个理由...

    作者 | Simplilearn编译 | 王瑞平当你决心学习一门语言的时候,很难选择到底应该学习哪一门,常用的语言有Python、Java、JavaScript、C/CPP、PHP、Swift、C#、Ruby、Objective-
  • 多线程开发带来的问题与解决方法

    使用多线程主要会带来以下几个问题:(一)线程安全问题  线程安全问题指的是在某一线程从开始访问到结束访问某一数据期间,该数据被其他的线程所修改,那么对于当前线程而言,该线程
  • JavaScript学习 -AES加密算法

    引言在当今数字化时代,前端应用程序扮演着重要角色,用户的敏感数据经常在前端进行加密和解密操作。然而,这样的操作在网络传输和存储中可能会受到恶意攻击的威胁。为了确保数据
  • 小红书1周涨粉49W+,我总结了小白可以用的N条涨粉笔记

    作者:黄河懂运营一条性教育视频,被54万人&ldquo;珍藏&rdquo;是什么体验?最近,情感博主@公主是用鲜花做的,火了!仅仅凭借一条视频,光小红书就有超过128万人,为她疯狂点赞!更疯狂的是,这
  • 机构称Q2全球智能手机出货量同比下滑11% 苹果份额依旧第2

    7月20日消息,据外媒报道,研究机构的报告显示,由于需求下滑,今年二季度全球智能手机的出货量,同比下滑了11%,三星、苹果等主要厂商的销量,较去年同期均有下
  • 电博会与软博会实现"线下+云端"的双线融合

    在本次“电博会”与“软博会”双展会利好条件的加持下,既可以发挥展会拉动人流、信息流、资金流实现快速交互流动的作用,继而推动区域经济良性发展;又可以聚
Top