Golang Channel 详细原理和使用技巧

简介: Golang Channel 详细原理和使用技巧

olang Channel 详细原理和使用技巧

Channel 详解

Channel 简要说明

Channel(一般简写为 chan) 管道提供了一种机制,它在两个并发执行的协程之间进行同步,并通过传递与该管道元素类型相符的值来进行通信。Channel 是用来在不同的 goroutine 中交换数据的,千万不要把 Channel 拿来在同一个 goroutine 中的不同函数之间间交换数据,chan 可以理解为一个管道或者先进先出的队列。

Channel 类型定义

最简单形式: chan elementType,通过这个类型的值,你可以发送和接收elementType 类型的元素。Channel 是引用类型,如果将一个 chan 变量赋值给另外一个,则这两个变量访问的是相同的 chann。

当然,我们可以用 make 分配一个channel:var c = make(chan int)

Channel 操作符<- 和操作方式

通信操作符 <- 的箭头指示数据流向,箭头指向哪里,数据就流向哪里,它是一个二元操作符,可以支持任意类型,对于 channel 的操作只有4种方式:

  • • 创建 channel (通过make()函数实现,包括无缓存 channel 和有缓存 channel);
  • • 向 channel 中添加数据(channel<-data);
  • • 从 channel 中读取数据(data<-channel);
  • • data<-channel, 从 channel 中接收数据并赋值给 data
  • • <-channel,从 channel 中接收数据并丢弃
  • • 关闭 channel(通过 close()函数实现)
  • • 读取关闭后的无缓存通道,不管通道中是否有数据,返回值都为 0 和 false。
  • • 读取关闭后的有缓存通道,将缓存数据读取完后,再读取返回值为 0 和 false。
  • • 对于一个关闭的 channel,如果继续向 channel 发送数据,会引起 panic
  • • channel 不能 close 两次,多次 close 会 panic

Channel 有无缓冲 & 同步、异步

channel 分为有缓冲 channel 和无缓冲 channel,两种 channel 的创建方法如下:

  • • var ch = make(chan int) //无缓冲 channel,等同于make(chan int ,0),是一个同步的 Channel
  • 无缓冲 channel 在读和写的过程中是都会阻塞,由于阻塞的存在,所以使用 channel 时特别注意使用方法,防止死锁和协程泄漏的产生。
  • • 无缓冲 channel 的发送动作一直要到有一个接收者接收这个值才算完成,否则都是阻塞着的,也就是说,发送的数据需要被读取后,发送才会完成
  • • 一般要配合 select + timeout 处理,然后再在这里添加超时时间
  • • var ch = make(chan int,10) //有缓冲channel,缓冲大小是10,是一个异步的Channel
  • • 带缓存的 channel 实际上是一个阻塞队列。队列满时写协程会阻塞,队列空时读协程阻塞。
  • • 有缓冲的时候,写操作是写完之后直接返回的。相对于不带缓存 channel,带缓存 channel 不易造成死锁。

Channel 各种操作导致阻塞和协程泄漏的场景

写操作,什么时候会被阻塞?

  • • 向 nil 通道发送数据会被阻塞
  • • 向无缓冲 channel 写数据,如果读协程没有准备好,会阻塞
  • • 无缓冲 channel ,必须要有读有写,写了数据之后,必须要读出来,否则导致 channel 阻塞,从而使得协程阻塞而使得协程泄漏
  • • 一个无缓冲 channel,如果每次来一个请求就开一个 go 协程往里面写数据,但是一直没有被读取,那么就会导致这个 chan 一直阻塞,使得写这个 chan 的 go 协程一直无法释放从而协程泄漏。
  • • 向有缓冲 channel 写数据,如果缓冲已满,会阻塞
  • • 有缓冲的 channel,在缓冲 buffer 之内,不读取也不会导致阻塞,当然也就不会使得协程泄漏,但是如果写数据超过了 buffer 还没有读取,那么继续写的时候就会阻塞了。如果往有缓冲的 channel 写了数据但是一直没有读取就直接退出协程的话,一样会导致 channel 阻塞,从而使得协程阻塞并泄漏。

读操作,什么时候会被阻塞?

  • • 从 nil 通道接收数据会被阻塞
  • • 从无缓冲 channel 读数据,如果写协程没有准备好,会阻塞
  • • 从有缓冲 channel 读数据,如果缓冲为空,会阻塞

close 操作,什么时候会被阻塞?

  • • close channel 对 channel 阻塞是没有任何效果的,写了数据但是不读,直接 close,还是会阻塞的。

Channel 各种操作对应的状态

  • • 正常的 channel,可读、可写
  • • nil 的 channel,表示未初始化的状态,只进行了声明,或者手动赋值为 nil
  • • 已经 closed 的 channel,表示已经 close 关闭了,千万不要误认为关闭 channel 后,channel 的值是 nil

Channel 长度和容量

容量(capacity)代表 Channel 容纳的最多的元素的数量,代表Channel的缓存的大小。如果没有设置容量,或者容量设置为0, 说明 Channel 没有缓存,长度和容量的两个函数是 cap 和 len 。

示例如下:

c := make(chan int, 100) // cap 就是 100,但是此时 len 为 0
c <- 0  // len = 1, cap = 100
c <- 0  // len = 2, cap = 100
<- c    // len = 1, cap = 100

Channel 的缺点

Channel 的缺点:

  1. 1. Channel 可能会导致循环阻塞或者协程泄漏,这个是最最最要重点关注的。
  2. 2. Channel 中传递指针会导致数据竞态问题(data race/ race conditions)
  3. 3. Channel 中传递的都是数据的拷贝,可能会影响性能,但是就目前我们的机器性能来看,这点数据拷贝所带来的 CPU 消耗,大多数的情况下可以忽略。

Go Channel 实现协程同步

channel 实现并发同步的说明

channel 作为 Go 并发模型的核心思想:不要通过共享内存来通信,而应该通过通信来共享内存,那么在 Go 里面,当然也可以很方便通过 channel 来实现协程的并发和同步了,并且 channel 本身还可以支持有缓冲和无缓冲的,通过 channel + timeout 实现并发协程之间的同步也是常见的一种使用姿势。

无缓冲 chan 示例

示例如下:

package main
import "fmt"
func main() {
     var ch = make(chan string)
     for i := 0; i < 10; i++ {
             go sum(i, i+10, ch)
     }
     for i := 0; i < 10; i++ {
             fmt.Print(<-ch)
     }
}
func sum(start, end int, ch chan string) {
     var sum int = 0
     for i := start; i < end; i++ {
             sum += i
     }
     ch <- fmt.Sprintf("Sum from %d to %d is %d\n", start, end, sum)
}

有缓冲 chan 示例

message_chan := make(chan int, 2)
    go func() {
        time.Sleep(time.Second * 3)
        println("start recv...")
        println(<-message_chan)
        println(<-message_chan)
        println(<-message_chan)
        println("finish recv...")
    }()
    println("start send 10...")
    message_chan <- 10
    println("start send 20...")
    message_chan <- 20
    println("start send 30...")
    message_chan <- 30
    println("finish send...")
    time.Sleep(time.Second * 3)
    close(message_chan)

参考

Channels in Go(https://go101.org/article/channel.html

How to Gracefully Close Channels(https://go101.org/article/channel-closing.html

总结了才知道,原来channel有这么多用法!(https://lessisbetter.site/2019/01/20/golang-channel-all-usage/


相关文章
|
6月前
|
Java Go
Golang底层原理剖析之垃圾回收GC(二)
Golang底层原理剖析之垃圾回收GC(二)
109 0
|
6月前
|
存储 SQL 安全
Golang底层原理剖析之上下文Context
Golang底层原理剖析之上下文Context
133 0
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
98 4
Golang语言之管道channel快速入门篇
|
1天前
|
存储 安全 测试技术
GoLang协程Goroutiney原理与GMP模型详解
本文详细介绍了Go语言中的Goroutine及其背后的GMP模型。Goroutine是Go语言中的一种轻量级线程,由Go运行时管理,支持高效的并发编程。文章讲解了Goroutine的创建、调度、上下文切换和栈管理等核心机制,并通过示例代码展示了如何使用Goroutine。GMP模型(Goroutine、Processor、Machine)是Go运行时调度Goroutine的基础,通过合理的调度策略,实现了高并发和高性能的程序执行。
55 28
|
3月前
|
算法 NoSQL 关系型数据库
熔断原理与实现Golang版
熔断原理与实现Golang版
|
3月前
|
存储 关系型数据库 Go
SOLID原理:用Golang的例子来解释
SOLID原理:用Golang的例子来解释
|
3月前
|
存储 人工智能 Go
golang 反射基本原理及用法
golang 反射基本原理及用法
27 0
|
6月前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
446 1
|
6月前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
92 2
|
6月前
|
程序员 Go 调度
第十六章 Golang中goroutine和channel
第十六章 Golang中goroutine和channel
48 3