preface

Previous articles, I talk about the main ideas in one Go, it is through the communication to share variables, rather than through the Shared variables to complete communication, but in some cases, concurrent processing by means of a channel, but increase the complexity of it, in order to solve this problem, one of the Go a lock “package”, Used to complete a process similar to locking in other multithreaded languages.

A race condition

We know that when there is only one goroutine (that is, there is only one main function), the code under the goroutine will be executed in a linear order from top to bottom, but if there are multiple Goroutines, There was no way to guarantee that these goroutine between execution order, and because of this mechanism, can cause some bugs, namely we often say in concurrent conditions, the absence of synchronization with each other, access to a Shared resource, and try to read and write the resources, there will be competition state.

When reading or writing to a shared resource, atomicity must be maintained, especially when writing. Only one Goroutine can write or write to the resource at a time.

Let’s look at the classic example — the example of saving money in a bank

Xiao Ming and Xiao Hong deposit money in the same account at the same time. They first check the amount of the account, then deposit different amounts of money in the account, and finally check the current amount

var w sync.WaitGroup
var money int // This is a simulated database
func main(a) {
	money = 200
	w.Add(2)
	go work(100."hong")
	go work(100."min")
	w.Wait()
	log.Printf("money is: %v",money)
}

func work(a int, n string)  {
	defer w.Done()
	value := money // simulate pulling data from the database
	value -= a
	log.Printf("%v buy",n)
	money = value // Simulation pushes data back to the database
}
Copy the code

So let’s see what it prints, right

2021/04/23 01:06:43 hong buy
2021/04/23 01:06:43 min buy
2021/04/23 01:06:43 money is: 100
Copy the code

In this case, we can see that when Xiao Hong and Xiao Ming also spent 100 yuan, there should be no yuan in the wallet, but there is 100 yuan left, this problem is the problem of reading and writing in the case of concurrency.

In order to solve this problem, there are two ways to solve it, one is to complete data synchronization through chan’s channel, but we will not discuss this way today, but another traditional way — locking

The mutex

If thread A holds the lock on the resource, thread B must wait for thread A to release the lock before it can acquire the lock to manipulate the resource.

The essence of mutexes is to create a critical section of code that guarantees that only one Goroutine will access the variable at any one time. Let’s rewrite the above code using the concept of mutexes

var w sync.WaitGroup
var money int
var m sync.Mutex // Initialize a lock
func main(a) {
	money = 200
	w.Add(2)
	go work(100."hong")
	go work(100."min")
	w.Wait()
	log.Printf("money is: %v",money)
}

func work(a int, n string)  {
	m.Lock() // Lock the goroutine
	defer w.Done()
	defer m.Unlock() / / releases the lock
	value := money // simulate pulling data from the database
	value -= a
	log.Printf("%v buy",n)
	money = value
}
Copy the code

With a few simple modifications, this time we typed the correct result

2021/04/23 01:14:58 min buy
2021/04/23 01:14:58 hong buy
2021/04/23 01:14:58 money is: 0
Copy the code

The space created between the goroutine lock and unlock is called the critical section.

Read-write lock

Most of the time, we can’t add a mutex to every Goroutine as we did in the above case, so the concurrency design of Go doesn’t make sense. We can’t add a mutex to every Goroutine when we’re dealing with a situation where we read too much and write too little.

Here’s another concept — read/write locks

For example, there was a man of one million lottery, their family are all very happy, the lucky again in the evening, the family will from time to time open the phone to look at this bank card deposit, at this time, if it is under the condition of using the mutex, network conditions is not particularly good, again when there is a person to view the data, Others will get stuck.

To solve this problem, we need to use our read-write lock.

Read locks can be compatible with each other, but between read locks and write locks, when one is operating on the resource, the other cannot include all read locks.

var mu sync.RWMutex
var balance int
func Balance(a) int {
    mu.RLock() // readers lock
    defer mu.RUnlock()
    return balance
}
Copy the code

The last

There are some other concepts of locking in Go, such as initializing sync.one, but these are not very common in practice and I won’t Go into them too much here.

Last last!! If the magistrate has already seen here, then point a thumbs-up!!