关于golang:Golang-channel-的本质-Channels-orchestrate-mutexes-serialize

6次阅读

共计 2933 个字符,预计需要花费 8 分钟才能阅读完成。

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 Pike 在 Go 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-…
版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-ND 3.0 CN 许可协定。转载请注明出处!

正文完
 0