Go并发优化的9大技巧,效果立竿见影
原创 Go先锋 Go先锋 2023-11-10 08:02 发表于广东 听全文Go 先锋
读完需要
8分钟速读仅需 3 分钟
概述
Go 语言 以其在并发编程方面的优势而闻名,但合理利用各种优化技巧可以进一步提升 Go 程序的并发性能。
本文将介绍在 CPU 密集型 和 IO 密集型 场景下优化 Go 并发程序的常见方法。
主要内容包括
充分利用多核 CPU
减少调度和上下文切换
控制内存占用
选择合适的数据结构
分析性能瓶颈
一、优化 CPU 密集场景
对于计算密集型任务, 可通过以下方法利用多核 CPU
1. 设置 GOMAXPROCS
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
2. 分解问题,并行计算
// 并发求和
func sum(s []int) int {
var wg sync.WaitGroup
ch := make(chan int)
for i := range s {
wg.Add(1)
go func(slice []int) {
ch <- sum(slice)
wg.Done()
}(s[i:])
}
go func() {
wg.Wait()
close(ch)
}()
var total int
for c := range ch {
total += c
}
return total
}
3. 避免不必要的 Goroutine 上下文切换
合理控制 Goroutine 数量,避免过多的 Goroutine 导致调度器负载过重。
二、优化 IO 密集场景
对于 IO 密集型 任务, 可通过以下方法并发提高吞吐量
1. 多路复用 IO 操作
func readFromFiles(filenames []string) {
var wg sync.WaitGroup
fileCh := make(chan File)
for _, f := range filenames {
wg.Add(1)
go func(f string) {
fileCh <- readFile(f)
wg.Done()
}(f)
}
go func() {
wg.Wait()
close(fileCh)
}()
for r := range fileCh {
process(r)
}
}
2.异步处理 IO 请求
使用 bufio.Scanner 等可以异步读取数据。
3.控制并发数
避免过多的 Goroutine 阻塞导致资源耗尽。
三、减少内存使用详解
优化内存使用非常重要,主要可从以下几个方面入手
1. 使用 sync.Pool 对象重用
// 重用解析对象
var parserPool = sync.Pool{
New: func() interface{} {
return &Parser{}
},
}
func parse(data []byte) {
p := parserPool.Get().(*Parser)
// 使用p解析
parserPool.Put(p) // 重用
}
2. 优化 Goroutine 栈大小
降低栈空间,可以支持更多 Goroutine
g := runtime.NewGoroutineWithStackSize(512 * 1024)
3. 复用缓冲区
重用字节缓冲区,减少内存分配
var buffPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func read() {
b := buffPool.Get().([]byte)
// 复用buff
buffPool.Put(b)
}
四、选择并发数据结构
读写频繁时优先使用 RWMutex
需要线程安全可以选择 sync.Map
不需要锁的场景可使用原子操作
channel 并发安全但需要合理 buffer 数
无锁数据结构如环形队列
例如
// 环形队列
type ringBuffer struct {
buf []interface{}
count uint64
head, tail uint32
}
func (rb *ringBuffer) Push(val interface{}) {
if rb.Full() {
rb.head++ // 头部推进
}
rb.buf[rb.tail] = val
rb.tail++ // 尾部推进
}
五、分析并发程序效率
可用以下工具来分析 Go 并发程序的效率
1. GODEBUG=schedtrace 追踪调度过程
import "runtime"
// 启用调度跟踪
runtime.GODEBUG = "schedtrace=1000"
func main() {
// 执行程序
// 分析log结果
}
2. runtime/pprof 做性能分析
import "runtime/pprof"
func main() {
pprof.StartCPUProfile(w io.Writer)
defer pprof.StopCPUProfile()
// 程序代码
pprof.WriteHeapProfile(w io.Writer)
}
3. go tool trace 跟踪执行过程
go test -trace=trace.out
go tool trace trace.out
# 分析
用这些工具可以直观地分析并发程序的效率问题。
六、爬虫程序优化案例
对一个爬虫程序进行并发优化
var urlPool = make(chan []string)
func main() {
// 使用无锁队列
urlPool = make(chan []string, 10000)
var wg sync.WaitGroup
// 限制最大并发数
maxG := 100
wg.Add(maxG)
for i := 0; i < maxG; i++ {
go func() {
crawl()
wg.Done()
}()
}
// 分发URL
go distributeUrls()
wg.Wait()
}
func crawl() {
for url := range urlPool {
// 并发获取URL
}
}
func distributeUrls() {
// 分批将URL放入channel
}
这样通过控制并发数量,重用对象池,使用无锁队列等方法可以优化爬虫程序的并发效率。
总结
Go 语言并发程序的执行效率对程序性能有重大影响。可从以下几个方面进行优化
合理利用多核 CPU,并行执行运算密集型任务,设置 GOMAXPROCS;对 IO 密集型任务,采用异步并发调用提升吞吐量。
控制 Goroutine 数量,避免过多线程导致调度器过载;同时也要避免使用过少线程而不能充分利用 CPU。
优化内存占用,重用对象减少 GC 开销;适当降低 Goroutine 栈空间也有助于支持更多 Goroutine。
选择正确的并发安全数据结构,如无锁数据结构、原子操作等可以提升并发效率。
使用调试和性能分析工具定位效率薄弱点;比如 CPU 分析、阻塞分析等。
充分考虑并发模型的优化,比如管道化消息传递;以及设计异步并发流程等。
针对不同场景设计最优的并发模式,避免共享内存同步等开销。
合理设置并发数,最大程度利用硬件资源而不导致过度调度。
采用最优的数据结构,利用无锁并发安全实现。
熟练运用这些优化技巧,可让 Go语言并发程序的效率和性能达到最佳。
Go语言并发31 Go语言并发 · 目录 上一篇Go 如何解决 并发中的竞争状态下一篇Go实现并发与并行,这才是正确打开方式! 阅读 1094 Go先锋 喜欢此内容的人还喜欢 没想到,Go语言垃圾回收是这样工作的! Go先锋 不看的原因
- 内容质量低
- 不看此公众号
- 内容质量低
- 不看此公众号
- 内容质量低
- 不看此公众号
人划线
标签:wg,trace,tool,并发,func,go,Go,优化 From: https://www.cnblogs.com/cheyunhua/p/17979835