我有一个相当简单的Go程序,旨在计算随机Fibonacci数,以测试我在我编写的工作池中观察到的一些奇怪行为。 当我分配一个线程时,程序在1.78s完成。当我分配4时,它在9.88秒结束。
代码如下:
var workerWG sync.WaitGroup
func worker(fibNum chan int) {
for {
var tgt = <-fibNum
workerWG.Add(1)
var a, b float64 = 0, 1
for i := 0; i < tgt; i++ {
a, b = a+b, a
}
workerWG.Done()
}
}
func main() {
rand.Seed(time.Now().UnixNano())
runtime.GOMAXPROCS(1) // LINE IN QUESTION
var fibNum = make(chan int)
for i := 0; i < 4; i++ {
go worker(fibNum)
}
for i := 0; i < 500000; i++ {
fibNum <- rand.Intn(1000)
}
workerWG.Wait()
}
如果我将runtime.GOMAXPROCS(1)
替换为4
,则该程序需要运行四倍。
这里发生了什么?为什么向工作池添加更多可用线程会降低整个池的速度?
我的个人理论是,它与工作者的处理时间小于线程管理的开销有关,但我不确定。我的预订是由以下测试引起的:
当我用以下代码替换worker
函数时:
for {
<-fibNum
time.Sleep(500 * time.Millisecond)
}
一个可用线程和四个可用线程占用相同的时间。
答案 0 :(得分:3)
我修改了你的程序,如下所示:
IF OBJECT_ID('tempdb..#tmpp1') IS NOT NULL
DROP TABLE #tmpp1;
SELECT
[PlanningHierarchyId]
,[ProductReferenceId]
,[PlanningYear]
,[PlanningSeason]
,[UpdatedBy]
INTO #tmpp1
FROM @paramTable
package main
import (
"math/rand"
"runtime"
"sync"
"time"
)
var workerWG sync.WaitGroup
func worker(fibNum chan int) {
for tgt := range fibNum {
var a, b float64 = 0, 1
for i := 0; i < tgt; i++ {
a, b = a+b, a
}
}
workerWG.Done()
}
func main() {
rand.Seed(time.Now().UnixNano())
runtime.GOMAXPROCS(1) // LINE IN QUESTION
var fibNum = make(chan int)
for i := 0; i < 4; i++ {
go worker(fibNum)
workerWG.Add(1)
}
for i := 0; i < 500000; i++ {
fibNum <- rand.Intn(100000)
}
close(fibNum)
workerWG.Wait()
}
更改为rand.Intn(1000)
在我的机器上产生:
rand.Intn(100000)
这意味着在原始代码中,执行与同步(通道读/写)的工作可以忽略不计。减速来自于必须跨线程而不是一个线程同步,并且只在中间执行非常少量的工作。
实质上,与计算高达1000的斐波那契数相比,同步是昂贵的。这就是为什么人们倾向于阻止微基准测试。提高这个数字可以提供更好的视角。但更好的想法是对正在进行的实际工作进行基准测试,包括IO,系统调用,处理,运算,写输出,格式化等。
编辑:作为一项实验,我将GOMAXPROCS设置为8,将工作人员数量增加到8,结果为:
$ time go run threading.go (GOMAXPROCS=1)
real 0m20.934s
user 0m20.932s
sys 0m0.012s
$ time go run threading.go (GOMAXPROCS=8)
real 0m10.634s
user 0m44.184s
sys 0m1.928s
答案 1 :(得分:1)
@thwd编写的代码是正确的,也是惯用的Go。
由于sync.WaitGroup的原子性质,您的代码正在被序列化。 workerWG.Add(1)
和workerWG.Done()
都将阻止,直到他们能够以原子方式更新内部计数器。
一些简化代码的提示:
chan struct{}
以避免分配)使用起来更便宜。 答案 2 :(得分:-1)
worker
中的主计算例程不允许调度程序运行。
像
for i := 0; i < tgt; i++ {
a, b = a+b, a
if i%300 == 0 {
runtime.Gosched()
}
}
从一个线程切换到两个线程时,将挂钟减少30%。
这种人造微型基准测试真的很难做到。