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 是接收和发送的协程队列。

参考资料:

目录
相关文章
|
5月前
|
人工智能 数据可视化 编译器
Go interface实现分析
本文深入探讨了Go语言中接口的定义、实现及性能影响。接口作为一种“约定”,包含方法签名集合,无需依赖具体类型即可调用方法,隐藏了内部实现细节。文章分析了接口的两种实现方式(iface和eface)、按值与按指针实现的区别,以及nil接口与普通nil的区别。同时,通过反汇编代码对比了接口动态调用与类型直接调用的性能差异,指出接口调用存在内存逃逸和无法内联的问题。最后总结了接口的优势与局限性,强调在实际开发中需根据场景合理选择是否使用接口。
102 13
|
3月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
95 24
|
3月前
|
存储 人工智能 安全
深入理解 go sync.Map - 基本原理
本文介绍了 Go 语言中 `map` 在并发使用时的常见问题及其解决方案,重点对比了 `sync.Mutex`、`sync.RWMutex` 和 `sync.Map` 的性能差异及适用场景。文章指出,普通 `map` 不支持并发读写,容易引发错误;而 `sync.Map` 通过原子操作和优化设计,在某些场景下能显著提升性能。同时详细讲解了 `sync.Map` 的基本用法及其适合的应用环境,如读多写少或不同 goroutine 操作不同键的场景。
143 1
|
2月前
|
Go 开发者
Go语言实战案例:使用select监听多个channel
本文为《Go语言100个实战案例 · 网络与并发篇》第5篇,详解Go并发核心工具`select`的使用。通过实际案例讲解如何监听多个Channel、实现多任务处理、超时控制和非阻塞通信,帮助开发者掌握Go并发编程中的多路异步事件处理技巧。
|
2月前
|
数据采集 编解码 监控
Go语言实战案例:使用channel实现生产者消费者模型
本文是「Go语言100个实战案例 · 网络与并发篇」第4篇,通过实战案例详解使用 Channel 实现生产者-消费者模型,涵盖并发控制、任务调度及Go语言并发哲学,助你掌握优雅的并发编程技巧。
|
4月前
|
算法 Java Go
Go内存原理-GC原理
本文介绍了Go语言中垃圾回收(GC)机制的发展与实现原理,涵盖从标记-清除算法到三色标记法,再到三色标记加混合写屏障的演进过程,重点解析各版本GC的核心思想、优缺点及性能优化方向。
|
5月前
|
安全 Go 开发者
Go语言之切片的原理与用法 - 《Go语言实战指南》
切片(slice)是Go语言中用于处理变长数据集合的核心结构,基于数组的轻量级抽象,具有灵活高效的特点。切片本质是一个三元组:指向底层数组的指针、长度(len)和容量(cap)。本文详细介绍了切片的声明与初始化方式、基本操作(如访问、修改、遍历)、长度与容量的区别、自动扩容机制、共享与副本处理、引用类型特性以及常见陷阱。通过理解切片的底层原理,开发者可以更高效地使用这一数据结构,优化代码性能。
127 13
|
5月前
|
Go 调度
GO语言函数的内部运行机制分析
以上就是Go语言中函数的内部运行机制的概述,展示了函数在Go语言编程中如何发挥作用,以及Go如何使用简洁高效的设计,使得代码更简单,更有逻辑性,更易于理解和维护。尽管这些内容深入了一些底层的概念,但我希望通过这种方式,将这些理论知识更生动、更形象地带给你,让你在理解的同时找到编程的乐趣。
77 5
|
5月前
|
人工智能 Go
[go]Slice 切片原理
本文详细介绍了Go语言中的切片(slice)数据结构,包括其定义、创建方式、扩容机制及常见操作。切片是一种动态数组,依托底层数组实现,具有灵活的扩容和传递特性。文章解析了切片的内部结构(包含指向底层数组的指针、长度和容量),并探讨了通过`make`创建切片、基于数组生成切片以及切片扩容的规则。此外,还分析了`append`函数的工作原理及其可能引发的扩容问题,以及切片拷贝时需要注意的细节。最后,通过典型面试题深入讲解了切片在函数间传递时的行为特点,帮助读者更好地理解和使用Go语言中的切片。
119 0