乐趣区

关于golang:Go语言-并发设计中的同步锁与waitgroup用法

明天是 golang 专题 的第 16 篇文章,咱们一起来聊聊 golang 当中的并发相干的一些应用。

尽管对于 goroutine 以及 channel 咱们都曾经介绍完了,然而对于并发的机制依然没有介绍完结。只有 goroutine 以及 channel 有时候还是不足以实现咱们的问题,比方多个 goroutine同时拜访一个变量 的时候,咱们怎么保障这些 goroutine 之间不会互相冲突或者是影响呢?这可能就须要咱们对资源进行加锁或者是采取其余的操作了。

同步锁

golang 当中提供了两种罕用的锁,一种是 sync.Mutex 另外一种是 sync.RWMutex。咱们先说说 Mutex,它就是 最简略最根底的同步锁 ,当一个 goroutine 持有锁的时候,其余的 goroutine 只能期待到锁开释之后才能够尝试持有。而 RWMutex 是读写锁的意思,它 反对一写多读,也就是说容许反对多个 goroutine 同时持有读锁,而只容许一个 goroutine 持有写锁。当有 goroutine 持有读锁的时候,会阻止写操作。当有 goroutine 持有写锁的时候,无论读写都会被梗塞。

咱们应用的时候须要依据咱们场景的个性来决定,如果咱们的场景是读操作多过写操作的场景,那么咱们能够应用 RWMutex。如果是写操作为主,那么应用哪个都差不多。

咱们来看下应用的案例,假如咱们以后有多个 goroutine,然而咱们只心愿持有锁的 goroutine 执行,咱们能够这么写:

`var lock sync.Mutex

for i := 0; i < 10; i++ {
    go func() {
        lock.Lock()
        defer lock.Unlock()
        // do something
}()
}
`

尽管咱们用 for 循环启动了 10 个 goroutine,然而因为互斥锁的存在,同一时刻只能有一个 goroutine 在执行

RWMutex 辨别了读写锁,所以咱们一共会有 4 个 api,别离是 Lock, Unlock, RLock, RUnlock。Lock 和 Unlock 是写锁的加锁以及解锁,而 RLock 和 RUnlock 天然就是读锁的加锁和解锁了。具体的用法和下面的代码一样,我就不多赘述了。

全局操作一次

在一些场景以及一些设计模式当中,会要求咱们某一段代码只能执行一次。比方很驰名的 单例模式,就是将咱们常常应用的工具设计成单例,无论运行的过程当中初始化多少次,失去的都是同一个实例。这样做的目标是减去创立实例的工夫,尤其是像是数据库连贯、hbase 连贯等这些实例创立的过程十分的耗时。

那咱们怎么在 golang 当中实现单例呢?

有些同学可能会感觉这个很简略啊,咱们只须要用一个 bool 型变量判断一下初始化是否有实现不就能够了吗?比方这样:

`type Test struct {}
var test Test
var flag = false

func init() Test{
    if !flag {
        test = Test{}
        flag = true
    }
    return test
}
`

看起来如同没有问题,然而认真推敲就会发现不对的中央。因为if 判断当中的语句并不是原子的,也就是说有可能同时被很多 goroutine 同时拜访。这样的话有可能 test 这个变量会被屡次初始化并且被屡次笼罩,直到其中一个 goroutine 将 flag 置为 true 为止。这可能会导致一开始拜访的 goroutine 取得的 test 都各不相同,而产生未知的危险。

要想要实现单例其实很简略,sync 库当中为咱们提供了现成的工具 once。它能够传入一个函数,只容许全局执行这个函数一次。在执行完结之前,其余 goroutine 执行到 once 语句的时候会被阻塞,保障只有一个 goroutine 在执行 once。当 once 执行完结之后,再次执行到这里的时候,once 语句的内容将会被跳过,咱们来联合一下代码来了解一下,其实也非常简单。

`type Test struct {}
var test Test

func create() {
test = Test{}
}

func init() Test{
once.Do(create)
return test
}
`

waitgroup

最初给大家介绍一下 waitgroup 的用法,咱们在应用 goroutine 的时候有一个问题是咱们 在主程序当中并不知道 goroutine 执行完结的工夫。如果咱们只是要依赖 goroutine 执行的后果,当然能够通过 channel 来实现。但如果咱们明确地心愿等到 goroutine 执行完结之后再执行上面的逻辑,这个时候咱们又该怎么办呢?

有人说能够用 sleep,但问题是咱们并不知道 goroutine 执行到底须要多少工夫,怎么能当时晓得须要 sleep 多久呢?

为了解决这个问题,咱们能够应用 sync 当中的另外一个工具,也就是 waitgroup。

waitgroup 的用法非常简单,只有三个办法,一个是 Add,一个是 Done,最初一个是 Wait。其实 waitgroup 外部存储了以后有多少个 goroutine 在执行,当调用一次 Add x 的时候,示意当下同时产生了 x 个新的 goroutine。当这些 goroutine 执行完的时候,咱们让它调用一下 Done,示意执行完结了一个 goroutine。这样当所有 goroutine 都执行完 Done 之后,wait 的阻塞会完结。

咱们来看一个例子:

`sample := Sample{}

wg := sync.WaitGroup{}

go func() {
    // 减少一个正在执行的 goroutine
wg.Add(1)
// 执行实现之后 Done 一下
defer wg.Done()
sample.JoinUserFeature()
}()

go func() {
wg.Add(1)
defer wg.Done()
sample.JoinItemFeature()
}()

wg.Wait()
// do something
`

总结

下面介绍的这些工具和库都是咱们日常在并发场景当中常常应用的,也是一个 golang 工程师 必会的技能之一 。到这里为止,对于 golang 这门语言的基本功能介绍就差不多了,前面将会介绍一些理论利用的内容,敬请期待吧。
转自 https://mp.weixin.qq.com/s?__…

退出移动版