Source location of this section https://github.com/golang-minibear2333/golang/blob/master/4.concurrent/4.4-deadlock/
When will deadlock occur
In the principle of computer composition, it is said that there are three necessary conditions for Deadlock: Circular waiting, resource sharing and non preemptive. There are only two cases of channel deadlock in Concurrency:
- The data was sent, but no one received it
- The data needs to be received, but no one sends it
Deadlock when sending a single value
It is clear to keep these two points in mind. Reviewing the previous examples will deadlock
a := make(chan int) a <- 1 //Write data to channel z := <-a //Read data from channel
- When there is only one co process, there is no buffered channel
- Sending first will block sending, and receiving first will block receiving.
- The sending operation is blocked before the receiver is ready, and the receiving operation is blocked before sending,
- The solution is to change to buffered channels or use CO process pairing
Solution 1: the process is paired. It doesn't matter whether you send it first or receive it first. Just pair it
chanInt := make(chan int) go func() { chanInt <- 1 }() res := <-chanInt
Solution 2: buffer channel
chanInt := make(chan int,1) chanInt <- 2 res := <-chanInt
- The number of messages in the buffer channel can be tested by len() function
- The capacity of the buffer channel can be tested with cap()
- When cap > len is satisfied, the transmission will not be blocked because it is not full
- When len > 0, because it is not empty, the reception will not be blocked
Using the buffer channel can reduce the possibility of blocking for producers and consumers, and is more friendly to asynchronous operations without waiting for each other to prepare. However, the capacity should not be set too large, otherwise it will occupy more memory.
Deadlock sent by multiple values
Pairing can make the deadlock disappear, but when multiple values are sent, they cannot be paired, and the deadlock will occur again
func multipleDeathLock() { chanInt := make(chan int) defer close(chanInt) go func() { res := <-chanInt fmt.Println(res) }() chanInt <- 1 chanInt <- 1 }
As expected, it's deadlocked
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.multipleDeathLock()
In operation, only when the notification signal is one-to-one, it will not be used after one notification. Other situations requiring multiple read-write pairing will not exist at all.
Resolve multi value sending deadlock
More commonly, a loop is used to continuously receive values and process one by one, as follows:
func multipleLoop() { chanInt := make(chan int) defer close(chanInt) go func() { for { //Not using ok will goroutine leak //res := <-chanInt res,ok := <-chanInt if !ok { break } fmt.Println(res) } }() chanInt <- 1 chanInt <- 1 }
Output:
1 1
- Add two values to the reception of the channel. ok represents whether the channel is normal. If it is closed, it is false
- You can delete that logic and try. It will output a series of numbers such as 1, 2, 0, 0, because closing takes time, and the closed channel received by cyclic reception gets 0
- The goroutine leak will be discussed later
Should I send or receive first
What happens if we change the location, put the receiver outside and write it inside
func multipleDeathLock2() { chanInt := make(chan int) defer close(chanInt) go func() { chanInt <- 1 chanInt <- 2 }() for { res, ok := <-chanInt if !ok { break } fmt.Println(res) } }
Output deadlock
1 2 fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.multipleDeathLock2()
- The above result occurs because the for loop keeps getting the value in the channel, but after reading 1 and 2, no new value is passed in the channel, so the receiver is blocked.
- Why is it OK to receive first and then send, because the defer of the function will be triggered to automatically close the channel after the sending ends in advance
- Therefore, we should always receive first and then send, and the sender should close it
goroutine leak
goroutine terminates in three scenarios:
- When a goroutine completes its work
- An unhandled error occurred
- There are other processes that tell it to stop
When none of the three conditions are met, goroutine will run all the time
func goroutineLeak() { chanInt := make(chan int) defer close(chanInt) go func() { for { res := <-chanInt //res,ok := <-chanInt //if !ok { // break //} fmt.Println(res) } }() chanInt <- 1 chanInt <- 1 }
- After the goroutineLeak() function above completes, trigger defer close(chanInt) to close the channel
- However, goroutine in the anonymous function is not closed. Instead, it keeps taking a circular value and gets the closed channel value (here is the default value of int 0)
- goroutine will run forever. If it is used again in the future, there will be new leakage! As a result, more and more memory and cpu are occupied
Output, if the program does not stop, it will always output 0
1 1 0 0 0 ...
If it is not turned off and there is no external write value, the receiver will always be blocked there, not even the output
func goroutineLeakNoClosed() { chanInt := make(chan int) go func() { for { res := <-chanInt fmt.Println(res) } }() }
- No blocking of any output
- The same is true for writing
- If it is a buffered channel, replace it with a full channel without reading; Or change to read to the empty channel without writing
- In addition to blocking, goroutine entering the dead cycle is also the cause of leakage
How to find leaks
Using the pprof monitoring tool provided by golang, you can find the memory increase. This will be described later
You can also monitor the memory usage of processes, such as the process exporter provided by prometheus
If you have a memory leak / goroutine leak code scanning tool, please leave a message and be grateful!
Summary
Today, we learned some details, but very important knowledge, which is also a high-frequency interview question in the future!
- In case of signal notification, ensure one-to-one correspondence, otherwise it will deadlock
- In addition to signal notification, we usually use circular processing channels to continuously process data during work
- It should always be received before sending, and closed by the sender, otherwise it is easy to deadlock or leak
- At the receiving place, judge whether the channel is closed. If it is closed, exit the receiving, or it will leak
- Be careful of goroutine leakage. Check the channel and exit when the channel is closed
- In addition to blocking, goroutine entering the dead cycle is also the cause of leakage
Wonderful review of previous periods
- What kind of experience is Netease interview?
- Little bear liver spent ten hours working out a go language improvement book
- That year, I believed in her evil
- Talk about the tutor system of large companies
- Tencent interviewer told you, what are the 11 characteristics of excellent employees?
For subsequent article updates, please click to read the original and jump to the open source book