Overview of concurrency issues
Question Type | describe |
---|---|
Data competition | Multiple coroutines perform asynchronous read and write operations on shared variables |
Deadlock | Multiple coroutines are waiting for each other to release resources |
Live lock | Coroutines keep trying to get resources but always fail |
Coroutine leak | Coroutines failed to exit in time, and the number of goroutines in the program soared |
Channel misuse | Issues such as not closing the channel, repeated closing, and writing after closing, etc. |
Scheduling jitter | Unexpected scheduling behavior leads to unstable responses |
Data competition
Data race occurs when two or more goroutines read and write a variable at the same time, and at least one is a write operation without synchronization measures.
var count int func add() { for i := 0; i< 1000; i++ { count++ } } func main() { go add() go add() () (count) }
Deadlock
Deadlock refers to two or more coroutines waiting for each other, causing the program to be permanently blocked.
func main() { ch := make(chan int) // No other coroutines receive, deadlock ch <- 1 }
func main() { ch1 := make(chan int) ch2 := make(chan int) go func() { <-ch1 ch2 <- 1 }() go func() { <-ch2 ch1 <- 1 }() // The program is stuck ( * 2) }
Coroutine leak
The program creates a large number of goroutines, but they have no exit conditions and are always blocked or waiting, causing the program resource consumption to soar.
func main() { ch := make(chan int) for { go func() { // Continuously generate blocking goroutine until memory runs out <-ch }() } }
Channel misuse
// Write to closed channelch := make(chan int) close(ch) ch <- 1 // panic // Repeat the channelclose(ch) close(ch) // panic // Read from empty channel, no write, causing deadlock<-ch
Scheduler Problems and Performance Jitter
- Coroutine explosion. A large number of goroutines are created in a short period of time, which may cause CPU jitter and scheduling chaos.
- A large number of blocking system calls. If a coroutine is stuck in a system call blockage, it will be suspended by the OS, which will affect the scheduling.
- Unfair dispatch. Although Go's scheduler is based on the GMP model, there is still the possibility of coroutine starvation.
Best Practice Summary
type | suggestion |
---|---|
Data Sharing | Use Channel or / for synchronization |
goroutine control | Use WaitGroup or context to manage coroutine lifecycles |
Channel Operation | Make sure that the channel is not closed before all write operations; the sender shall be responsible for closing the channel |
Concurrent task distribution | Use coroutine pools (limiting concurrency counts) to avoid exhaustion of system resources |
Debugging Tools | Use race, pprof, trace, delve |
Log Analysis | Print goroutine ID and observe the concurrency process |
Actual case analysis
Crawling system coroutine leak
Phenomenon:
- Low CPU usage
- Memory usage continues to rise
- The number of goroutines is growing
analyze:
- Use pprof to view the goroutine source code location
- The location reason is that a select branch lacks <-done, which causes the coroutine to fail to exit.
deal with:
- All for + select are added () to process exit
func worker() { go func() { for { select { case msg := <-someChan: // Process messages (msg) // ❌ There is no exit condition, the coroutine will never exit } } }() }
func worker(ctx ) { go func() { for { select { case msg := <-someChan: (msg) case <-(): // ✅ Receive a cancel signal and exit the coroutine ("worker exiting") return } } }() } ctx, cancel := (()) worker(ctx) // After a period of time or under certain conditions, call cancel() to notify the coroutine to exit(5 * ) cancel()
Asynchronous task competition leads to data malfunction
Phenomenon:
- Concurrent writes to global maps asynchronously
analyze:
- Occasionally data errors, difficulty in debugging
deal with:
- Use or
// Global map, non-thread-safevar data = make(map[int]int) func main() { for i := 0; i < 100; i++ { go func(i int) { data[i] = i // 🚨 Multiple coroutines write maps at the same time, which will lead to data competition or panic }(i) } (1 * ) ("done") }
var ( data = make(map[int]int) mu ) func main() { for i := 0; i < 100; i++ { go func(i int) { () data[i] = i () }(i) } (1 * ) ("done") }
var data func main() { for i := 0; i < 100; i++ { go func(i int) { (i, i) }(i) } (1 * ) (func(k, v interface{}) bool { ("key: %v, value: %v\n", k, v) return true }) }
Create global counters under high concurrency
- It is recommended to use the sync/atomic package. sync/atomic provides the ability to operate atomically, ensuring thread safety without locking, and is suitable for counters and other scenarios.
var globalCounter int64 func worker(wg *) { defer () // Add 1 to the atom to ensure concurrency security atomic.AddInt64(&globalCounter, 1) } func main() { var wg (1000) for i := 0; i < 1000; i++ { go worker(&wg) } // Make sure the main goroutine is waiting for all child goroutines to complete () ("Counter value:", globalCounter) }
- use . Thread-safe but slightly low performance, it is suitable for thread protection under complex logic and is not recommended for simple addition and subtraction scenarios.
var counter int var mu func main() { () counter++ () }
- Use Channel to implement counting. The performance is not as good as atomic operation and is suitable for scenarios with channel communication requirements.
var counter = make(chan int, 1) func init() { counter <- 0 } func main() { v := <-counter v++ counter <- v }
This is the end of this article about the implementation of Golang concurrency. For more related Golang concurrency content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!