Goroutine调度策略
- M:N调度模型原理
- 概念解释:在Go的调度器中,M:N调度模型指的是N个用户级线程(Goroutine,即G)映射到M个内核级线程(M)上。这里的用户级线程是由Go运行时管理的轻量级线程,而内核级线程是操作系统内核管理的线程。
- 调度过程:
- Goroutine创建:当一个Go程序创建一个新的Goroutine时,它被放入一个全局的Goroutine队列或者本地的Goroutine队列(每个M都有一个本地队列)。
- M与P:M(内核线程)需要关联一个P(Processor,处理器资源,包含本地Goroutine队列等)才能运行Goroutine。P的数量决定了同一时间可以并发执行的Goroutine数量,默认情况下P的数量等于CPU核心数。
- 调度执行:M从关联的P的本地队列中获取Goroutine来执行。如果本地队列空了,M会尝试从全局Goroutine队列中偷取一部分Goroutine到本地队列执行,或者从其他P的本地队列中偷取(work - stealing机制)。当一个Goroutine执行系统调用等阻塞操作时,M会阻塞,但P会被释放,以便其他M可以关联该P继续执行其他Goroutine。
高并发场景下提升性能的方法
- 调整调度器相关参数
- 调整GOMAXPROCS:
- 作用:
GOMAXPROCS
环境变量或函数用于设置可以同时执行的最大CPU核数,即P的数量。通过合理设置GOMAXPROCS
,可以充分利用多核CPU的性能。
- 示例:在一个CPU密集型的高并发程序中,如果服务器是8核CPU,默认情况下
GOMAXPROCS
会设置为8。但如果程序中有大量I/O操作,可能适当增加GOMAXPROCS
的值(比如设置为16),这样在一些Goroutine进行I/O等待时,其他M可以关联更多的P执行其他Goroutine,从而提升整体性能。代码示例如下:
package main
import (
"fmt"
"runtime"
)
func main() {
runtime.GOMAXPROCS(16)
// 这里编写高并发代码
}
- 优化代码结构
- 减少锁争用:
- 问题:在高并发场景下,锁的争用会严重影响性能。例如多个Goroutine频繁地访问共享资源并使用互斥锁进行保护,会导致大量Goroutine等待锁,从而降低并发效率。
- 优化方法:可以通过将共享资源按一定规则拆分,每个Goroutine只访问自己对应的部分,减少锁的粒度。比如在一个统计网站访问量的系统中,如果使用一个全局变量和一个互斥锁来统计总访问量,高并发下锁争用会很严重。可以将统计按时间段或IP段等维度拆分,每个Goroutine只负责更新自己对应部分的统计数据,最后再汇总。示例代码如下:
package main
import (
"fmt"
"sync"
)
type Counter struct {
counts map[string]int
mu sync.Mutex
}
func (c *Counter) Increment(key string) {
c.mu.Lock()
c.counts[key]++
c.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
counter := Counter{counts: make(map[string]int)}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key-%d", id%10)
counter.Increment(key)
}(i)
}
wg.Wait()
fmt.Println(counter.counts)
}
- 使用无锁数据结构:
- 优势:在某些场景下,使用无锁数据结构(如Go标准库中的
sync.Map
)可以避免锁争用,提升性能。sync.Map
适合高并发读写场景,它内部采用了一种更复杂的结构来避免锁的使用。
- 示例:在一个高并发的缓存系统中,如果使用普通的
map
加互斥锁来实现缓存,会有锁争用问题。使用sync.Map
则可以提升性能。示例代码如下:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var cache sync.Map
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
cache.Store(id, id*2)
}(i)
}
wg.Wait()
cache.Range(func(key, value interface{}) bool {
fmt.Printf("Key: %d, Value: %d\n", key, value)
return true
})
}