SoFunction
Updated on 2025-05-23

Golang The implementation of concurrency

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 &lt;- 1
}
func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		&lt;-ch1
		ch2 &lt;- 1
	}()
	
	go func() {
		&lt;-ch2
		ch1 &lt;- 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			&lt;-ch
		}()
	}
}

Channel misuse

// Write to closed channelch := make(chan int)
close(ch)
ch &lt;- 1 // panic

// Repeat the channelclose(ch)
close(ch) // panic

// Read from empty channel, no write, causing deadlock&lt;-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 := &lt;-someChan:
                // Process messages                (msg)
            // ❌ There is no exit condition, the coroutine will never exit            }
        }
    }()
}
func worker(ctx ) {
    go func() {
        for {
            select {
            case msg := &lt;-someChan:
                (msg)
            case &lt;-():
                // ✅ 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 &lt; 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(&amp;globalCounter, 1)
}

func main() {
	var wg 
	
	(1000)
	
	for i := 0; i &lt; 1000; i++ {
		go worker(&amp;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!