Multithreading 使用GO时如何测量系统过载
我在GO中重写一个旧系统,在旧系统中我测量系统平均负载,以了解是否应该增加线程池中的线程数 在go中,人们不使用threadpool或goroutine池,因为启动goroutine非常便宜。 但是仍然运行太多的goroutine效率较低,仅足以保持cpu使用率接近100% 因此,有一种方法可以知道有多少goroutine已准备好运行(未被阻止),但当前尚未运行。或者是否有办法获取计划的可运行goroutine“Run queue”的数量。请查看 要打印“所有当前goroutine的堆栈跟踪”,请使用: 要打印“导致同步原语阻塞的堆栈跟踪”,请使用: 您可以将这些功能与中的功能结合起来,以获得一些基本的报告 此示例故意创建许多阻塞的goroutine并等待它们完成。每隔5秒,它打印Multithreading 使用GO时如何测量系统过载,multithreading,performance,go,scalability,Multithreading,Performance,Go,Scalability,我在GO中重写一个旧系统,在旧系统中我测量系统平均负载,以了解是否应该增加线程池中的线程数 在go中,人们不使用threadpool或goroutine池,因为启动goroutine非常便宜。 但是仍然运行太多的goroutine效率较低,仅足以保持cpu使用率接近100% 因此,有一种方法可以知道有多少goroutine已准备好运行(未被阻止),但当前尚未运行。或者是否有办法获取计划的可运行goroutine“Run queue”的数量。请查看 要打印“所有当前goroutine的堆栈跟踪”,
块的输出
pprof配置文件,以及仍然存在的GOROUTION数:
package main
import (
"fmt"
"math/rand"
"os"
"runtime"
"runtime/pprof"
"strconv"
"sync"
"time"
)
var (
wg sync.WaitGroup
m sync.Mutex
)
func randWait() {
defer wg.Done()
m.Lock()
defer m.Unlock()
interval, err := time.ParseDuration(strconv.Itoa(rand.Intn(499)+1) + "ms")
if err != nil {
fmt.Errorf("%s\n", err)
}
time.Sleep(interval)
return
}
func blockStats() {
for {
pprof.Lookup("block").WriteTo(os.Stdout, 1)
fmt.Println("# Goroutines:", runtime.NumGoroutine())
time.Sleep(5 * time.Second)
}
}
func main() {
rand.Seed(time.Now().Unix())
runtime.SetBlockProfileRate(1)
fmt.Println("Running...")
for i := 0; i < 100; i++ {
wg.Add(1)
go randWait()
}
go blockStats()
wg.Wait()
fmt.Println("Finished.")
}
主程序包
进口(
“fmt”
“数学/兰德”
“操作系统”
“运行时”
“运行时/pprof”
“strconv”
“同步”
“时间”
)
变量(
wg sync.WaitGroup
互斥
)
func randWait(){
推迟工作组完成()
m、 锁()
延迟m.Unlock()
interval,err:=time.ParseDuration(strconv.Itoa(rand.Intn(499)+1)+“ms”)
如果错误!=零{
fmt.Errorf(“%s\n”,err)
}
睡眠时间(间隔)
返回
}
func blockStats(){
为了{
pprof.Lookup(“块”).WriteTo(os.Stdout,1)
fmt.Println(“#Goroutines:,runtime.NumGoroutine())
时间。睡眠(5*时间。秒)
}
}
func main(){
rand.Seed(time.Now().Unix())
运行时.SetBlockProfileRate(1)
fmt.Println(“运行…”)
对于i:=0;i<100;i++{
工作组.添加(1)
走吧,等等
}
goblockstats()
wg.Wait()
fmt.Println(“完成”)
}
我不确定这是否是你想要的,但你可以修改它以满足你的需要
有没有办法知道有多少goroutine已准备好运行(未被阻止),但当前尚未运行
您将能够(2014年第4季度/2015年第1季度)尝试并可视化这些goroutines,并开发一个新的跟踪工具(2014年第4季度):
跟踪包含:
- 与goroutine调度相关的事件:
- goroutine开始在处理器上执行
- 同步原语上的goroutine块
- 一个goroutine创建或取消阻止另一个goroutine李>
- 网络相关事件:
- 一个goroutine在网络IO上阻塞
- 网络IO上的goroutine未被阻止李>
- 系统调用相关事件:
- goroutine进入syscall
- goroutine从syscall返回李>
- 垃圾收集器相关事件:
- GC启动/停止
- 同步扫描启动/停止;及
- 用户事件李>
GOMAXPROCS
每个事件都包含事件id、精确的时间戳、操作系统线程id、处理器id、goroutine id、堆栈跟踪和其他相关信息(例如未阻止的goroutine id)
只有在“运行太多goroutine的效率低于将cpu使用率保持在100%左右”的情况下才会出现这种情况。Go很少从正在运行的goroutine切换,除非它在等待I/O或通道操作或同步原语时被阻止,因此启动大量goroutine,操作系统线程数通过
runtime.GOMAXPROCS(runtime.NumCPU())
匹配CPU数,不需要创建太多额外的上下文切换开销。我们也许可以在工作负载的附加信息方面提供更多帮助--您的goroutines主要是在旋转CPU,还是在等待DB,或是通道操作,或者…?谢谢用户2714852,你是说如果GOMAXPROCS设置为2,我启动4个永不停止且从不阻塞的goroutine,那么运行时将只运行前2个,从不切换到另一个?在Go 1.1中,这是完全正确的:goroutine调度完全是协作的,如果存在无I/O的无休止循环,等等。,它永远占据着线。这是我在书中谈到的。(您始终可以调用runtime.Gosched()以显式屈服);那句话中的“偶尔”让我说“很少”强制转换。我只知道这些;我刚才从Go源代码中无法获得更多信息。您可以添加一些级别的跟踪,以跟踪goroutine在运行时处理了多少个作业,作为每个goroutine的活跃度的度量。要回答两个问题,请假设goroutine主要做CPU密集型工作。+1这似乎回答了这个问题。我发现这个SOA示例程序非常棒。还可能值得测试问题的前提,即动态调优goroutine count是否能够提高性能,使其值得(与仅仅启动大量goroutine或其他简单策略相比)。答案可能取决于应用程序。但是,不管怎样,这似乎回答了人们提出的问题。@user2714852我同意,我还没有遇到过这样的情况:手动调整goroutine的数量比只启动所需数量并让运行时处理调度提供更好的性能。我相信有一些例子,只是不是很常见,从我所能告诉你的情况应该在Go 1.2及以后的版本中变得更好。“我还没有遇到过这样的情况,手动调整Goroutine的数量比只启动所需数量的Goroutine提供更好的性能”如果您的任何goroutine可能正在执行阻塞文件系统或系统调用工作,这是一个非常糟糕的主意。假设你有一个6阶段的任务。更好
pprof.Lookup("block").WriteTo(os.Stdout, 1)
package main
import (
"fmt"
"math/rand"
"os"
"runtime"
"runtime/pprof"
"strconv"
"sync"
"time"
)
var (
wg sync.WaitGroup
m sync.Mutex
)
func randWait() {
defer wg.Done()
m.Lock()
defer m.Unlock()
interval, err := time.ParseDuration(strconv.Itoa(rand.Intn(499)+1) + "ms")
if err != nil {
fmt.Errorf("%s\n", err)
}
time.Sleep(interval)
return
}
func blockStats() {
for {
pprof.Lookup("block").WriteTo(os.Stdout, 1)
fmt.Println("# Goroutines:", runtime.NumGoroutine())
time.Sleep(5 * time.Second)
}
}
func main() {
rand.Seed(time.Now().Unix())
runtime.SetBlockProfileRate(1)
fmt.Println("Running...")
for i := 0; i < 100; i++ {
wg.Add(1)
go randWait()
}
go blockStats()
wg.Wait()
fmt.Println("Finished.")
}