Home  >  Article  >  Backend Development  >  An article to talk about the resource competition problem in Go language

An article to talk about the resource competition problem in Go language

青灯夜游
青灯夜游forward
2023-02-17 14:40:203224browse

An article to talk about the resource competition problem in Go language

We all know that thread safety is very important in concurrent programming. Next, we will assume a scenario to reproduce the thread insecurity situation, and then talk about how to solve the

scenario in Go

We now need to 1~ 100 find their factorial and put the result into a map

1! = 1 = 1
2! = 1 * 2 = 2
3! = 1 * 2 * 3 = 6
4! = 1 * 2 * 3 * 4 = 24
5! = 1 * 2 * 3 * 4 * 5 = 120
...
{
    1: 1
    2: 2
    3: 6
    4: 24
    5: 120
    ...
}

Code implementation

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 10; i++ {
        Factorial(i)
    }
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

An article to talk about the resource competition problem in Go languageThe above code execution result is actually no problem, why Will there be disorder? Because this is the map in the Go language, it is actually out of order. According to our understanding, the first stored, first out, but sorry, Golang's map is not like this. There is no problem with the above execution. Careful students may have discovered that this version of the code does not use concurrency, right? Okay, let’s continue to improve

Concurrency implementation

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 10; i++ {
        go Factorial(i)
    }
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

An article to talk about the resource competition problem in Go languageWe can find that the concurrent version adds a ## in front of the call to calculate the factorial function. #go Just. Don’t underestimate this go, it’s too far-fetched. Of course, everyone knows that this is the keyword to start a coroutine in the go language.

The execution result is that nothing is output to the console. This is because of the execution relationship between the main coroutine and the sub-coroutine. Let’s draw a picture to understand

From the above picture we It can be found that the execution time of the main coroutine is short (shown as relatively short), and the execution time of the sub-coroutine is relatively long (shown as relatively long) We must remember that the sub-coroutine is relative to the current main coroutine. If the main coroutine no longer exists, then there will be no sub-coroutine An article to talk about the resource competition problem in Go language

So the above code outputs nothing. Because the main coroutine has been executed, but the sub-coroutine has not been completed. If the sub-coroutine has not been completed, can there be anything in

factorialMap?

Main Waiting Sub

This leads to our first question, how does the main coroutine wait for the sub-coroutine to finish executing before exiting the program. We are now using the simplest and easiest way to think of it

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 100; i++ {
        go Factorial(i)
    }
    time.Sleep(time.Second * 3)
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

When the number of concurrencies is relatively small, this problem may not occur. Once the number of concurrencies becomes larger, the problem will appear immediatelyAn article to talk about the resource competition problem in Go language

The execution result in the picture is

Concurrent map writing errorWhy does this problem occur? We assume that 100 people put fruit into a basket, which is easy. But if 100 people take fruits from a basket, there will be problems. First, there may not be enough fruits in the basket. Second, everyone wants to take it first, which will inevitably cause competition.

Problem 1 Optimization

In view of the above problem, we introduce the concept of global lock. This is a bit like when we go to the toilet. 100 people want to go to the toilet, but there is only one toilet. Whoever grabs it first will go first, and this person also locks the toilet to prevent others from coming in

An article to talk about the resource competition problem in Go language

var factorialMap = make(map[int]int)
var lock sync.Mutex

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
            result *= i
    }
    // defer 不好理解
    // defer func(){
    // 	lock.Unlock() // 执行完解锁
    // }()
    lock.Lock() // 执行时上锁
    factorialMap[n] = result
    lock.Unlock() // 执行后解锁
}

func main() {
    for i := 1; i < 100; i++ {
        go Factorial(i)
    }
    time.Sleep(time.Second * 3)
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

The execution result of 0 may be caused by the data type not being able to be saved. You don’t need to worry about thisAn article to talk about the resource competition problem in Go language

This way we will solve the problem of resource competition. But there is actually another problem, that is, we still have to wait manually in the main coroutine, which is very bad. What if the sub-coroutine cannot solve it within 3 seconds? An article to talk about the resource competition problem in Go language

Problem 2 Optimization

This problem is that we don’t want to manually wait for the sub-coroutine in the main coroutine. In other words, we don’t want to directly state in the code How long to wait

Here we have introduced the internal principles of

WaitGroup

var factorialMap = make(map[int]int)
var lock sync.Mutex
var wg sync.WaitGroup

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    lock.Lock() // 执行时上锁
    factorialMap[n] = result
    lock.Unlock() // 执行后解锁
    wg.Done()
}

func main() {
    for i := 1; i < 100; i++ {
        wg.Add(1)
        go Factorial(i)
    }
    wg.Wait()
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

You can learn about it in detail, I won’t go into it now To sum up,

WaitGroup is a basket. Every time a coroutine is opened, an identifier (Add function) is added to the basket. Every time a coroutine is executed, an identifier (Done) is subtracted from the basket. function), and finally check the basket. If it is empty, it means that the coroutine has been executed (Wait function)

[Recommended learning:

go video tutorial]

The above is the detailed content of An article to talk about the resource competition problem in Go language. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete