明天是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?__...