SoFunction
Updated on 2025-03-03

How to solve the problem of shared variables in Golang development

In Go language, goroutine + channel shares memory through communication, thereby realizing concurrent programming.

But at the same time, Go also provides traditional ways to achieve concurrency through shared variables, that is, shared memory. This article will introduce the relevant mechanisms provided by Go.

1. What is competition

After a Go program is run, many goroutines will run at the same time. The execution of the code in each goroutine is sequential. If we cannot determine the execution order of the code in two goroutines. It can be said that these two goroutines are executed concurrently.

If the result of a piece of code is correct whether it is sequentially executed or concurrently, then it can be said that the code is concurrently safe.

There are many problems with concurrent unsafe code, such as deadlocks, live locks, competitions, etc. Both deadlocks and live locks indicate that the code can no longer be executed, while competition means that the code can be executed, but there may be incorrect results.

A typical example is depositing money into a bank account:

var balance int
func Deposit(amount int) {
    balance = balance + amount
}
func Balance() int {
    return balance
}

If the program is correct, the final output should be 20000, but run multiple times, and the result may be 19700, 19800, 19900 or other values. At this time, we will say that there is a data competition in this program.

The root cause of this problem is that balance = balance + amount The execution of this line of code on the CPU is not atomic, and may be interrupted halfway through execution.

2. How to eliminate competition

If a competition occurs, we need to find a way to solve it. In general, there are three ways to solve the competition:

1. Do not modify the amount

If a variable does not need to be modified, accessing anywhere is safe, but this method cannot solve the above problem.

2. Don't access the same variable in multiple goroutines

goroutine + channel is such an idea. Update variables through channel blocking, which is also in line with the design concept of Go code: do not communicate through shared memory, but should share memory through communication.

3. Only one goroutine is allowed to access variables at the same time

If there can only be one goroutine access variable at the same time, other goruotines need to wait until the current access is over before accessing, which can also eliminate competition. The tool we will talk about below is to ensure that only one goroutine can access variables at the same time.

3. Concurrency tools provided by Go

We have already mentioned three ways to solve the competition above. The following tools are used in Go to achieve that there can only be one goroutine access variable at the same time. Let's take a look at it separately:

3.1 Mutex

This is the most classic tool to solve competition. Its principle is that if you want to access a resource, you must get the lock of this resource. Only by getting the lock can you be qualified to access the resource. If you want to access other goroutines, you must wait until the current goroutine releases the lock and grab the lock before accessing it.

Before using it, you need to apply for a lock for the resource, which is used. This is the implementation of mutex locks in Go:

var mu 
var balance int

Each goroutine that gets the lock needs to be released after access to the variable is completed. Even if it occurs in an abnormal situation, it needs to be released. Here you can use defer to ensure that the lock will be released in the end:

func Deposit(amount int) {
    ()
    defer ()
    balance = balance + amount
}

func Balance() int {
    ()
    defer ()
    return balance
}

After modifying the code, run the above deposit code. No matter how many times it runs, the final result will be 20,000. At this point, our competitive problem has been solved, but there are still some small problems.

3.2 Read and write mutex lock

The above mutex lock solves the competitive problem of accessing data, but there is another small problem, that is, the operation of reading balances is a bit inefficient. Every time I read the balance, I still need to grab the lock. In fact, if this variable is not changed, even if it is read by multiple goroutines at the same time, it will not cause concurrency security problems.

An ideal scenario we want is that if this variable is not written, multiple goroutines can be read simultaneously, which can greatly improve efficiency.

Go also provides this tool, which is read and write lock. This lock is not mutually exclusive to read and read. Simply put, this lock can ensure that only one goroutine is written at the same time. If there is a goroutine being written, other goroutines cannot be read or written, but multiple goroutines are allowed to be read at the same time.

Let's change the above code again, just change it in one place:

var mu  // Replacevar balance int

After this modification, the above deposit code will still output 20,000, but it can allow multiple goroutines to read the balance at the same time.

Most Go languages ​​can be solved using these two tools.

3.3 Once

Go also provides such a tool that can ensure that the code will only be executed once and is mostly used in resource initialization and other scenarios. The way to use is also very simple:

o := &{}
for i := 0; i < 100; i++ {
    (func(){
        go func() {
            Deposit(100)
        }()

        go func() {
            Deposit(100)
        }()
    })
}
// Sleep for one second and let the above goroutine execution be completed(1 * )
(Balance())

If the above code is controlled by Once, it will only be saved once, so the above code will always output 200.

3.4 Race Detector

Many competition errors are difficult to detect. Go provides a tool that can help check whether there is competition in the code. It's very simple to use, just add the -race parameter after the following command:

$ go run -race

$ go build -race

$ go test -race

After adding this parameter, the compiler will access all shared variables during execution of the code. If you find that after a goroutine is written to a variable and there is no synchronous operation, another goroutine reads and writes this variable, which means that there is a competition here and an error will be reported. For example, the following code:

data := 1

go func() {
    data = 2
}()

go func() {
    data = 3
}()

(2 * )

After running go run -race, the following error will be reported:

Found 1 data race(s)
exit status 66

4. Summary

Go also provides a concurrent programming mechanism provided by traditional languages, and concurrent programming can also be implemented through shared memory methods. The interface provided by Go is relatively simple, but the capabilities provided are powerful enough.

This is the end of this article about how to solve the problem of shared variables in Golang development. For more related content on shared variables in Golang, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!