Go language | synchronization lock and waitgroup usage in concurrent design

Today is the 16th article of the golang topic . Let's talk about some concurrency-related uses in golang.

Although we have finished introducing goroutines and channels, the mechanism of concurrency is still not finished. Only goroutines and channels are sometimes not enough to complete our problem. For example, when multiple goroutines access a variable at the same time, how can we ensure that these goroutines will not conflict or affect each other? This may require us to lock resources or take other actions.

Sync lock

There are two commonly used locks in golang, one is sync.Mutex and the other is sync.RWMutex. Let's talk about Mutex first. It is the simplest and most basic synchronization lock . When a goroutine holds the lock, other goroutines can only try to hold it after the lock is released. RWMutex means a read-write lock. It supports one write and multiple reads , which means that multiple goroutines are allowed to hold a read lock at the same time, while only one goroutine is allowed to hold a write lock. When a goroutine holds a read lock, it will block write operations. When a goroutine holds a write lock, both read and write will be blocked.

When we use it, we need to decide based on the characteristics of our scene. If our scene is a scene where read operations are more than write operations, then we can use RWMutex. If the write operation is the main one, whichever one is the same.

Let's take a look at the use case. Assuming that we currently have multiple goroutines, but we only want the goroutine holding the lock to execute, we can write:

var lock sync.Mutex

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

Although we started 10 goroutines with a for loop, due to the existence of the mutex, only one goroutine can be executed at the same time .

RWMutex distinguishes read-write locks, so we have a total of 4 apis, namely Lock, Unlock, RLock, RUnlock. Lock and Unlock are the locking and unlocking of write locks, while RLock and RUnlock are naturally the locking and unlocking of read locks. The specific usage is the same as the code above, so I won't go into details.

Global operation once

In some scenarios and some design patterns, we will be required to execute a certain piece of code only once. For example, the well-known singleton mode is to design a tool we often use as a singleton. No matter how many times it is initialized during the operation, the same instance is obtained. The purpose of this is to reduce the time to create instances, especially the process of creating instances such as database connections and hbase connections is very time-consuming.

So how do we implement singletons in golang?

Some students may think this is very simple. We only need to use a bool variable to judge whether the initialization is completed or not? For example:

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

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

It seems that there is no problem, but after careful consideration, you will find something wrong. Because the statement in the if judgment is not atomic , that is to say, it may be accessed by many goroutines at the same time. In this case, it is possible that the variable test will be initialized and overwritten many times until one of the goroutines sets flag to true. This may lead to different tests obtained by the goroutines visited at the beginning, which may cause unknown risks.

To achieve a singleton is actually very simple, the sync library provides us with a ready tool once. It can pass in a function, and only allows global execution of this function once . Before the execution ends, other goroutines will be blocked when the once statement is executed, ensuring that only one goroutine is executing once. When the once execution is over, the content of the once statement will be skipped when it is executed again here. Let's combine the code to understand it. It is actually very simple.

type Test struct {}
var test Test

func create() {
    test = Test{}
}

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

waitgroup

Finally, I will introduce you to the use of waitgroup. When we use goroutine, one problem is that we don't know when the goroutine execution ends in the main program . If we only need to rely on the results of the execution of the goroutine, of course we can do it through channels. But if we clearly want to wait until the execution of the goroutine ends before executing the following logic, what should we do at this time?

Some people say that sleep can be used, but the problem is that we don't know how long it takes for the goroutine to execute. How can we know in advance how long it takes to sleep?

To solve this problem, we can use another tool in sync, which is waitgroup.

The usage of waitgroup is very simple, there are only three methods, one is Add, one is Done, and the last is Wait . In fact, waitgroup internally stores how many goroutines are currently executing. When Add x is called once, it means that x new goroutines are generated at the same time. When these goroutines are executed, we let it call Done, which means that the execution is over for a goroutine. In this way, when all goroutines have finished executing Done, the blocking of wait will end.

Let's look at an example:

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

to sum up

The tools and libraries introduced above are what we often use in concurrent scenarios, and they are also one of the skills that a golang engineer must know . So far, about the basic functions of the language golang is almost the same, and some practical applications will be introduced later, so stay tuned.

Guess you like

Origin blog.csdn.net/Flogod/article/details/108573202