Golang channel 的本质

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: Golang channel 的本质

channel 是 Go 语言独有的一个特性,相比 goroutine 更加抽象,也更加难以理解。毕竟后者可以类比线程、进程。《Go channels are bad and you should feel bad》 提及在使用 channel 和 mutex 时的困惑。其中提到过一个简单的程序,可以保存一场游戏的各个选手中的最高分。作者分别使用 channelmutex 来实现该功能。

channel 版

首先定义 Game 结构体:

type Game struct {
  bestScore int
  scores    chan int
}

bestScore 不会使用 mutex 保护,而是使用一个独立的 goroutine 从 channel 接收数据,然后更新其状态。

func (g *Game) run() {
  for score := range g.scores {
    if g.bestScore < score {
      g.bestScore = score
    }
  }
}

然后定义构造函数来开始一场游戏

func NewGame() (g *Game) {
  g = &Game{
    bestScore: 0,
    scores:    make(chan int),
  }
  go g.run()
  return g
}

紧接着,定义 Player 接口返回该选手的分数,同时返回 error 用以表示 选手放弃比赛等异常情况。

type Player interface {
  NextScore() (score int, err error)
}

游戏通过 channel 接收所有选手的分数

func (g *Game) HandlePlayer(p Player) error {
  for {
    score, err := p.NextScore()
    if err != nil {
      return err
    }
    g.scores <- score
  }
}

最终,Game 得以实现线程安全的记录选手的最高分,一切都很完美。

该实现大为成功,游戏服务同时创建了很多的游戏。不久,你发现有选手偶尔会停止游戏,很多游戏也不再有选手玩了,但是却没有什么机制停止游戏循环。你正被废弃的 (*Game).run goroutine 压垮。

mutex 版

然而,请注意使用 mutex 的解决方案的简单性,它甚至不存在以上问题:

type Game struct {
  mtx sync.Mutex
  bestScore int
}
func NewGame() *Game {
  return &Game{}
}
func (g *Game) HandlePlayer(p Player) error {
  for {
    score, err := p.NextScore()
    if err != nil {
      return err
    }
    g.mtx.Lock()
    if g.bestScore < score {
      g.bestScore = score
    }
    g.mtx.Unlock()
  }
}

channel 用以编排,mutex 用以串行

如果是你来实现,你更愿意使用 channel 还是 mutex

按照目前提供的信息,毫无疑问,我会选择后者。

那 channel 和 mutex 有什么区别呢?在什么场景下该使用 channel ?

其实 Rob PikeGo Proverbs 中总结为:

Channels orchestrate; mutexes serialize.

翻译就是

channel 用以编排,mutex 用以串行

此句话很简单,但也很抽象。究竟该怎样理解呢?

channel vs mutex

Rob Pike 在讲述《Concurrency is not Parallelism》中开篇,即提到:

  1. 世界万物是并行的,但是当前的编程语言却是面向对象的
  2. Golang 希望通过 goroutine(并发执行)、channel(同步和数据传递)、select(多路并发控制)来实现并行

在之前的文章中,我提到过

对于其他语言的使用者,对于他们而言,程序中的流程控制一般意味着:

  • if/else
  • for loop

在 Go 中,类似的理解仅仅对了一小半。因为 channel 和 select 才是流程控制的重点。

channel 提供了强大能力,帮助数据从一个 goroutine 流转到另一个 goroutine。也意味着,channel 对程序的 数据流控制流 同时存在影响。

channel 只是 Go 语言并行化工具集的一部分,其同时肩负了 数据流控制流 的职责,它是程序结构的组织者。对比来看,mutex 则只关注数据,保障数据串行访问

编排

再谈 channel 的编排,可以看下 《Go Concurrency Patterns》中搜索举例:

/*
Example: Google Search 3.0
Given a query, return a page of search results (and some ads).
Send the query to web search, image search, YouTube, Maps, News, etc. then mix the results.
*/
c := make(chan Result)
go func() { c <- First(query, Web1, Web2) } ()
go func() { c <- First(query, Image1, Image2) } ()
go func() { c <- First(query, Video1, Video2) } ()
timeout := time.After(80 * time.Millisecond)
for i := 0; i < 3; i++ {
    select {
    case result := <-c:
        results = append(results, result)
    case <-timeout:
        fmt.Println("timed out")
        return
    }
}

无论程序执行在几个核心的机器上,程序的并行结构都没有任何变化,如下:

讲到程序结构的编排,可以跟服务编排的 Kubernetes 类比。 如果说 goroutine 是 K8S 的容器,channel 就是 K8S 的网络(如,overlay)。Kubernetes 使用户能够以任何规模部署和扩展其微服务应用程序,Golang 使程序能够在任何数量 CPU 的机器上执行和和扩展进行充分的并行。

总结

就像《Concurrency is not Parallelism》说明的那样,目前 channel很大程度的被误用或滥用了。了解清楚 channel 的本质,才能使用正确的工具做对的事。

Goroutines and channels are big ideas. They’re tools for program construction.

But sometimes all you need is a reference counter.

Go has “sync” and “sync/atomic” packages that provide mutexes, condition variables, etc. They provide tools for smaller problems.

Often, these things will work together to solve a bigger problem.

Always use the right tool for the job.

本文涉及源代码go-test: 《go-channel-vs-mutex》

本文作者 : cyningsun

本文地址https://www.cyningsun.com/05-15-2021/channels-orchestrate-mutexes-serialize.html

版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Golang

  1. 译|There Are No Reference Types in Go
  2. Go 语言没有引用类型,指针也与众不同
  3. 译|What “accept interfaces, return structs” means in Go
  4. 如何用好 Go interface
  5. 一个优雅的 LRU 缓存实现
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
6月前
|
Go 调度
浅谈Golang通道channel
浅谈Golang通道channel
68 0
|
安全 Go
Golang 语言使用 channel 并发编程
Golang 语言使用 channel 并发编程
49 0
|
安全 Go 索引
Golang 语言中的 channel 实现原理
Golang 语言中的 channel 实现原理
60 0
|
消息中间件 缓存 Go
Golang 语言中 Channel 的使用方式
Golang 语言中 Channel 的使用方式
47 0
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
93 4
Golang语言之管道channel快速入门篇
|
6月前
|
程序员 Go 调度
第十六章 Golang中goroutine和channel
第十六章 Golang中goroutine和channel
48 3
|
6月前
|
存储 算法 Java
Golang底层原理剖析之多路select、channel数据结构和阻塞与非阻塞
Golang底层原理剖析之多路select、channel数据结构和阻塞与非阻塞
75 0
|
12月前
|
存储 安全 Go
Golang通道(Channel)原理解析
Golang通道(Channel)原理解析
|
存储 Go
No.13 golang中channel(管道)常见使用场景?(下)
No.13 golang中channel(管道)常见使用场景?
|
存储 Go
No.13 golang中channel(管道)常见使用场景?(上)
No.13 golang中channel(管道)常见使用场景?
193 0