Goroutine 的同步(第三部分)

krystollia · · 455 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
- 第一部分:https://studygolang.com/articles/14118 - 第二部分:https://studygolang.com/articles/14478 ## mutex 和 sync.Once 介绍 ![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/sync-goroutine/part3-1.jpeg) 假设你的程序中有一个需要某种初始化的功能。该 Bootstrap 程序成本很高,因此将其推迟到实际使用功能的那一刻是有意义的。这样,当功能未激活时,就不会浪费 CPU 周期。 这在 Go 中如何完成? ```go package main import "fmt" var capitals map[string]string func Bootstrap() { capitals = make(map[string]string) capitals["France"] = "Paris" capitals["Germany"] = "Berlin" capitals["Japan"] = "Tokyo" capitals["Brazil"] = "Brasilia" capitals["China"] = "Beijing" capitals["USA"] = "Washington" ... capitals["Poland"] = "Warsaw" } func getCapitalCity(country string) string { if capitals == nil { Bootstrap() } return capitals[country] } func main() { fmt.Println(getCapitalCity("Poland")) fmt.Println(getCapitalCity("USA")) fmt.Println(getCapitalCity("Japan")) } ``` 你可以想象,如果它可以处理所有的国家,其他类似数据库的结构,使用 I/O 操作等,则 *bootstrap* 函数可能非常昂贵。上述解决方案看起来简单且优雅,但不幸的是,它并不会正常工作。问题在于当 *bootstrap* 函数在运行时无法阻止其它 Goroutine 做同样的事。而这些繁重的计算只做一次是很可取的。另外在 *capitals* 刚刚被初始化而其中的 key 还未设置时,其它 Goroutine 会看到它不为 *nil* 从而尝试从空的 map 中获取值。 ## [sync.Mutex](https://golang.org/pkg/sync/#Mutex) ![image](https://raw.githubusercontent.com/studygolang/gctt-images/master/sync-goroutine/part3-2.jpeg) Go 有包含很多好东西的内置 [sync](https://golang.org/pkg/sync/) 包。我们可以使用 [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion) (mutual exclusion) 来解决我们的同步问题。 ```go import ( "fmt" "sync" ) ... var ( capitals map[string]string mutex sync.Mutex ) ... func getCapitalCity(country string) string { mutex.Lock() if capitals == nil { Bootstrap() } mutex.Unlock() return capitals[country] } ``` bootstrap 程序可被多次运行的问题解决了。如果任何一个 Goroutine 正在运行 *bootstrap* 或者甚至在判断 `capitals == nil`,那么其它 Goroutine 会在 `mutex.Lock()` 处等待。*Unlock* 函数一结束另一个 Goroutine 就会被“准许进入”。 但是一次只能有一个 Goroutine 执行被放在 `mutex.Lock()` 和 `mutex.Unlock()` 之间的代码。因此如果存放首都城市的 map 被多个 Goroutine 读取,那么一切都会在 if 语句处被处理成一个接一个的。从根本上说对于 map 的读操作(包括判断它是否为 *nil*)应该允许一次处理多个,因为它是线程安全的。 ## [sync.RWMutex](https://golang.org/pkg/sync/#RWMutex) 读 / 写 mutex 可以同时被多个 reader 或者一个 writer 持有(writer 是指改写数据的某种东西): ```go mutex sync.RWMutex ... func getCapitalCity(country string) string { mutex.RLock() if capitals != nil { country := capitals[country] mutex.RUnlock() return country } mutex.RUnlock() mutex.Lock() if capitals == nil { Bootstrap() } mutex.Unlock() return getCapitalCity(country) } ``` 现在代码变得复杂多了。第一部分使用读锁以允许多个 reader 同时读取 *capitals*。在一开始 Bootstrap 还未完成,所以调用者会拿到 `mutex.Lock()` 并且做必要的初始化。当这部分结束,函数可以被再次调用来获取期望的值。这些都在 Bootstrap 函数已经返回之后。 最新的解决方法很显然维护起来比较难。幸运的是有一个内置的方法恰好帮我们应对这种情况…… ## [sync.Once](https://golang.org/pkg/sync/#Once) ```go once sync.Once ... func getCapitalCity(country string) string { once.Do(bootstrap) return capital[country] } ``` 上面的代码比第一个简明版的解决方法(它并不正常工作)更加简单。可以保证 *bootstrap* 只会被调用一次,并且从 map 中读数据仅在 *bootstrap* 返回后才会被执行。 --- 点赞以帮助别人发现这篇文章。如果你想得到新文章的更新,请关注我。 ## 资源 - [Go 的内存模型 —— Go 编程语言](https://golang.org/ref/mem) >The Go memory model specifies the conditions under which reads of a variable in one Goroutine can be guaranteed to … <br>*golang.org* - [sync - Go 编程语言](https://golang.org/pkg/sync/) >Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and … <br>*golang.org* *[保留部分版权](http://creativecommons.org/licenses/by/4.0/)* *[Golang](https://medium.com/tag/golang?source=post)* *[Programming](https://medium.com/tag/programming?source=post)* *[Concurrency](https://medium.com/tag/concurrency?source=post)* *[Synchronization](https://medium.com/tag/synchronization?source=post)* *[Software Development](https://medium.com/tag/software-development?source=post)* **喜欢读吗?给 Michał Łowicki 一些掌声吧。** 简单鼓励下还是大喝采,根据你对这篇文章的喜欢程度鼓掌吧。

via: https://medium.com/golangspec/synchronized-goroutines-part-iii-c60bcfeefd2a

作者:Michał Łowicki  译者:krystollia  校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

入群交流(和以上内容无关):加入Go大咖交流群,免费领全套学习资料或添加微信:muxilin131420 备注:入群;或加QQ群:729884609

455 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传