今天是golang专题的第16篇文章,咱们一块儿来聊聊golang当中的并发相关的一些使用。golang


虽然关于goroutine以及channel咱们都已经介绍完了,可是关于并发的机制仍然没有介绍结束。只有goroutine以及channel有时候仍是不足以完成咱们的问题,好比多个goroutine同时访问一个变量的时候,咱们怎么保证这些goroutine之间不会互相冲突或者是影响呢?这可能就须要咱们对资源进行加锁或者是采起其余的操做了。web

同步锁


golang当中提供了两种经常使用的锁,一种是sync.Mutex另一种是sync.RWMutex。咱们先说说Mutex,它就是最简单最基础的同步锁,当一个goroutine持有锁的时候,其余的goroutine只能等待到锁释放以后才能够尝试持有。而RWMutex是读写锁的意思,它支持一写多读,也就是说容许支持多个goroutine同时持有读锁,而只容许一个goroutine持有写锁。当有goroutine持有读锁的时候,会阻止写操做。当有goroutine持有写锁的时候,不管读写都会被堵塞。数据库


咱们使用的时候须要根据咱们场景的特性来决定,若是咱们的场景是读操做多过写操做的场景,那么咱们可使用RWMutex。若是是写操做为主,那么使用哪一个都差很少。设计模式


咱们来看下使用的案例,假设咱们当前有多个goroutine,可是咱们只但愿持有锁的goroutine执行,咱们能够这么写:api


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链接等这些实例建立的过程很是的耗时。app


那咱们怎么在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这门语言的基本功能介绍就差很少了,后面将会介绍一些实际应用的内容,敬请期待吧。


今天的文章到这里就结束了,若是喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、在看、点赞)。

- END -