Golang Go语言中多线程对数组操作时的性能问题
Golang Go语言中多线程对数组操作时的性能问题
我尝试着用多线程分别运行以下两个函数,并使用 time
来查看它们的 cpu 使用率,一个的使用率就符合启动的线程数(比如 8 线程就得到 800% 的使用率),另一个就无法得到相符合的 cpu 使用率 (比如 8 线程得到 600% 的使用率,2 线程得到 300% 的使用率)。虽然两个函数都没有线程间的互斥锁,这两个函数的区别在于:第一个函数没有对数组的操作,第二个函数有对数组的操作。具体函数如下:
函数 1: addOne
该函数持续做加法运算
func addOne(n int,wg *sync.WaitGroup){
var i int
for i < n{
i++
}
wg.Done()
}
函数 2: appendArray
该函数不断向一个数组中添加元素,为了防止内存溢出,每添加 10,000 个元素,就清空数组。
func appendArray(n int,wg *sync.WaitGroup){
var i int
list := make([]int,10000)
for i < n{
for j := 0 ; j < 10000; j++{
list = append(list, i)
}
list = make([]int,10000)
i++
}
wg.Done()
}
启动多线程的函数: test
该函数将工作总数 totalJobs
平分给每个线程
func test(totalThreads, totalJobs int){
n := totalJobs/totalThreads
var wg sync.WaitGroup
for i := 0; i < totalThreads; i++{
wg.Add(1)
go appendArray(n,&wg)
// go addOne(n,&wg)
}
wg.Wait()
}
通过使用 pprof
, 两个代码的 cpu 分析如下:
函数 1:
函数 2:
我发现对于第二个函数,虽然代码中没有调用锁,在执行的过程中会出现 runtime lock
, runtime futex
之类的锁操作。
这里我不明白的是:
- 为什么在有数组操作后,编译后的程序出现了锁操作,从而影响力 cpu 的使用?
- 如何避免 /减缓这些锁操作带来的性能影响?
更多关于Golang Go语言中多线程对数组操作时的性能问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
因为 append 做不到原子操作吧?
更多关于Golang Go语言中多线程对数组操作时的性能问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
mem alloc 和 move 都是开销大头啊喵……没事别这么玩内存啊喵……碎片化和反复拷贝都是超级消耗资源的,而且,还因为内存 /缓存 /CPU 多核同步等问题,导致必须上锁否则会出现未知内存分布状态喵……
正常一点做法就是别在循环里艹数组尺寸喵!提前预判一下分配空间就好!
否则请使用其它数据结构而不是数组!
我想楼主想表达的是切片而不是数组
我测试过 append 是可以直接操作一个切片声明而不报空指针的,所以 append 里面会调用 make 返回一个切片实例。这种操作必然是耗时操作。
不过题主用的这个 pprof 看起来挺好用的,可以试试
make 的第二个参数是长度,第三个参数才是容量。
make 的第二个参数是长度,第三个参数才是容量。+1
你这里还是会指数分配内存
#3 啊瞎了。就扫了一眼没过大脑
append 很多情况下是对对内存进行 malloc, memcpy, free。这些操作未完成之前是不会返回的。所以你会看到各种锁。在有锁的状态下,其它的 goroutine 实际上是在等待。
用 time 查看 CPU 的使用率???
歪个楼,楼主函数调用图是用什么生成的?
pprof 然后用 svg 或者别的图片格式导出
golang<br>func appendArray(n int,wg *sync.WaitGroup){<br> var i int<br> list := make([]int,10000)<br> for i < n{<br> for j := 0 ; j < 10000; j++{<br> list[j] = i<br> }<br> list = make([]int,10000)<br> i++<br> }<br> wg.Done()<br>}<br>
原因就是#5 说的,应该make([]int, 0, 10000)
鼓捣了大半天,被人家几小时之前就说过了,不爽啊……
https://play.golang.org/p/3iTdWuMIgoe
有什么推荐的工具吗?
内存分配是必然要加锁的,因为堆内存是整个程序共享的
比较现代的内存分配器会对多线程场景进行优化,把一部分内存划分为线程独占,从而减少锁的使用。
这要看内存分配实现方式,比如 操作系统 对 进程 的堆分配必然会上锁互斥,但是如果是进程自己有 malloc / free 实现则可以在一定程度上缓解这类问题,比如 TCMalloc 一类工具。
——但是这不是写烂代码的理由喵!
在Golang(Go语言)中,多线程(或称为goroutines)对数组进行操作时的性能问题通常涉及并发控制和数据竞争。Go语言以其内置的goroutines和channel机制,为并发编程提供了强大的支持,但在处理共享资源如数组时,仍需谨慎处理。
-
避免数据竞争:多个goroutines同时读写数组时,需要使用sync包中的工具(如sync.Mutex或sync.RWMutex)进行同步,防止数据竞争。虽然这保证了数据一致性,但可能会引入锁竞争,影响性能。
-
使用channel进行通信:相较于锁,使用channel进行goroutines间的通信和数据传递通常能提供更好的性能和可扩展性。通过将数组操作封装在channel的发送和接收操作中,可以避免直接共享数组,从而减少并发控制的复杂性。
-
考虑无锁数据结构:对于某些特定场景,可以考虑使用无锁数据结构(如环形缓冲区)来提高并发性能。这些数据结构通过算法设计避免了锁的使用,但需要开发者对并发编程有较深的理解。
-
性能测试与调优:使用Go的内置工具(如pprof)进行性能测试,找出性能瓶颈。针对测试结果进行代码优化,如减少不必要的锁操作、优化数据结构等。
总之,在Go语言中多线程操作数组时,需结合具体场景选择合适的并发控制策略,并通过性能测试和调优来确保程序的性能和稳定性。