Go Channel 的基本运用和原理分析

简介: Go 在 goroutine 的通信经常会提及的设计思想是:不要通过共享内存的方式进行通信,而应该通过通信的方式共享内存。这和 Java 语言不通,Java 中多个线程传递数据的方式一般都是通过共享内存或者其他共享资源的方式解决线程竞争问题。

Go 在 goroutine 的通信经常会提及的设计思想是:不要通过共享内存的方式进行通信,而应该通过通信的方式共享内存。这和 Java 语言不通,Java 中多个线程传递数据的方式一般都是通过共享内存或者其他共享资源的方式解决线程竞争问题。

Channel

基本操作

ch := make(chan int, 10) // 创建 channel
ch <- 1 // 写入
v := <- ch // 读取
  • 使用 make 初始化 channel ,并可以设置容量 ,10 就是容量 capacity 表示 Channel 容纳的最多元素的数量,即 Channel 缓存的大小。
  • 如果没有设置容量,或者容量是为0 ,那说明没有缓存,即为同步。发送和接收需要同步进行,否则容易发生堵塞
  • 可定义只读 channel 或只写 channel
ch2 := make(<-chan int) // 只可以用来接收 int 类型的数据
ch3 := make(chan<- int) // 只可以用来发送 int 类型的数据

常用操作

for rang 读取 channel

缓存 channel 需要不断读取时,可以使用 for range 。当channel 关闭了,for 循环就会自动退出,这样就不需要再进行监控。

ch := make(chan int, 10)
for x := range ch {
    fmt.Println(x)
}

_, ok 判断 channel 是否关闭

读已关闭的 channel 会得到零值,所以可以使用该方式先判断是否已关闭。若 ok=true 则表示读到数据,chan 未关闭;若 ok=false 则表示chan 已关闭,无数据读到。

if v,ok := <-ch; ok{
     fmt.Println(v)
}

select 处理多个 channel

当有多个通道,需要对多个 channel 进行监控时,可使用 select 。需要注意 select 只处理伟阻塞的 case ,若通道为 nil 时对应的 case 将会堵塞。

ch := make(chan int, 10)
ch1 := make(chan int, 10)
func doLinsen(){
    select {
        case <-ch:
            fmt.Println(1)
        case <-ch1:
            fmt.Println(2)
    }
}

channel 控制读写权限

控制 channel 只读或者只写

// 只读 readCh的数据,声明为 <-chan int
func consumer(readCh <-chan int) {
    for x:= range readCh{
        fmt.Println(x)
    }
}

使用 time 实现 channel 无阻塞读写

select + channel 方法处理 channel 时,为操作加上超时的处理

func consumer(){
    select {
        case ch <-x:
          fmt.Println(ch)
        case time.After(time.Microsecond): // 超时处理
            return errors.New("read time out")
    }
}

使用 chan struct{} 作为信号 channel

协程通过传递退出信号,告知协程退出,而这个退出信号就可以使用 空 struct。

type Handler struct {
    stopCh chan struct{}
    reqCh chan *Request
}

应用场景

channel 主要运用在数据流动的场景中:

  • 消息传递
  • 事件消息订阅与广播
  • 并发控制
  • 同步与异步
  • 任务分发

Channel 底层原理

type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}

如上,Channel 的结构体是 hchan。含有11个字段:

  • buf 是用来存储缓存数据,是个循环链表
  • sendx 和 recvx 是用来记录 buf 循环链表中 发送或接收的索引
  • lock 是互斥锁
  • recvq 和 sendq 是接收和发送的协程队列。

参考资料:

目录
相关文章
|
2天前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel
在这个快节奏的技术时代,Go语言以其简洁的语法和强大的并发能力脱颖而出。本文将带你深入Go语言的并发机制,探索goroutine的轻量级特性和channel的同步通信能力,让你在高并发场景下也能游刃有余。
|
5天前
|
存储 安全 Go
探索Go语言的并发模型:Goroutine与Channel
在Go语言的多核处理器时代,传统并发模型已无法满足高效、低延迟的需求。本文深入探讨Go语言的并发处理机制,包括Goroutine的轻量级线程模型和Channel的通信机制,揭示它们如何共同构建出高效、简洁的并发程序。
|
3天前
|
安全 Go 调度
探索Go语言的并发模型:Goroutine与Channel的魔力
本文深入探讨了Go语言的并发模型,不仅解释了Goroutine的概念和特性,还详细讲解了Channel的用法和它们在并发编程中的重要性。通过实际代码示例,揭示了Go语言如何通过轻量级线程和通信机制来实现高效的并发处理。
|
4天前
|
算法 Java 编译器
你为什么不应该过度关注go语言的逃逸分析
【10月更文挑战第21天】逃逸分析是 Go 语言编译器的一项功能,用于确定变量的内存分配位置。变量在栈上分配时,函数返回后内存自动回收;在堆上分配时,则需垃圾回收管理。编译器会根据变量的使用情况自动进行逃逸分析。然而,过度关注逃逸分析可能导致开发效率降低、代码复杂度增加,并且对性能的影响相对较小。编译器优化通常比人工干预更准确,因此开发者应更多关注业务逻辑和整体性能优化。
|
11天前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel的实践指南
在本文中,我们将深入探讨Go语言的并发机制,特别是goroutine和channel的使用。通过实际的代码示例,我们将展示如何利用这些工具来构建高效、可扩展的并发程序。我们将讨论goroutine的轻量级特性,channel的同步通信能力,以及它们如何共同简化并发编程的复杂性。
|
13天前
|
安全 Go 数据处理
掌握Go语言并发:从goroutine到channel
在Go语言的世界中,goroutine和channel是构建高效并发程序的基石。本文将带你一探Go语言并发机制的奥秘,从基础的goroutine创建到channel的同步通信,让你在并发编程的道路上更进一步。
|
16天前
|
算法 安全 Go
Python与Go语言中的哈希算法实现及对比分析
Python与Go语言中的哈希算法实现及对比分析
21 0
|
16天前
|
机器学习/深度学习 自然语言处理 Go
Python与Go在AIGC领域的应用:比较与分析
Python与Go在AIGC领域的应用:比较与分析
20 0
|
2月前
|
编译器 Go
Go中init()执行顺序分析
文章分析了Go语言中`init()`函数的执行顺序和时机,指出全局变量初始化后先于`init()`函数执行,而`init()`函数在`main()`函数之前执行,且包的`init()`函数按包的导入顺序进行初始化。
25 1
|
2月前
|
存储 编译器 Go
Go语言中的逃逸分析
Go语言中的逃逸分析