Goroutine 的同步(第三部分)

krystollia · 2018-11-10 09:58:22 · 1600 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2018-11-10 09:58:22 的文章,其中的信息可能已经有所发展或是发生改变。

mutex 和 sync.Once 介绍

image

假设你的程序中有一个需要某种初始化的功能。该 Bootstrap 程序成本很高,因此将其推迟到实际使用功能的那一刻是有意义的。这样,当功能未激活时,就不会浪费 CPU 周期。 这在 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

image

Go 有包含很多好东西的内置 sync 包。我们可以使用 mutex (mutual exclusion) 来解决我们的同步问题。

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

读 / 写 mutex 可以同时被多个 reader 或者一个 writer 持有(writer 是指改写数据的某种东西):

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

once sync.Once

...

func getCapitalCity(country string) string {
    once.Do(bootstrap)
    return capital[country]
}

上面的代码比第一个简明版的解决方法(它并不正常工作)更加简单。可以保证 bootstrap 只会被调用一次,并且从 map 中读数据仅在 bootstrap 返回后才会被执行。


点赞以帮助别人发现这篇文章。如果你想得到新文章的更新,请关注我。

资源

The Go memory model specifies the conditions under which reads of a variable in one Goroutine can be guaranteed to …
golang.org

  • sync - Go 编程语言

    Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and …
    golang.org

保留部分版权

Golang Programming Concurrency Synchronization Software Development

喜欢读吗?给 Michał Łowicki 一些掌声吧。

简单鼓励下还是大喝采,根据你对这篇文章的喜欢程度鼓掌吧。


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

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

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


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

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