channel 是 Go 语言独有的一个特性,相比 goroutine 更加抽象,也更加难以理解。毕竟后者能够类比线程、进程。《Go channels are bad and you should feel bad》 说起在使用 channel 和 mutex 时的困惑。其中提到过一个简单的程序,能够保存一场游戏的各个选手中的最高分。做者分别使用 channel
和 mutex
来实现该功能。html
首先定义 Game
结构体:git
type Game struct { bestScore int scores chan int }
bestScore 不会使用 mutex 保护,而是使用一个独立的 goroutine 从 channel 接收数据,而后更新其状态。github
func (g *Game) run() { for score := range g.scores { if g.bestScore < score { g.bestScore = score } } }
而后定义构造函数来开始一场游戏web
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 接收全部选手的分数segmentfault
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 的解决方案的简单性,它甚至不存在以上问题:并发
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
?
按照目前提供的信息,毫无疑问,我会选择后者。app
那 channel 和 mutex 有什么区别呢?在什么场景下该使用 channel ?
其实 Rob Pike
在 Go Proverbs 中总结为:
Channels orchestrate; mutexes serialize.
翻译就是
channel 用以编排,mutex 用以串行
此句话很简单,但也很抽象。究竟该怎样理解呢?
Rob Pike
在讲述《Concurrency is not Parallelism》中开篇,即提到:
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 许可协议。转载请注明出处!