你使用的Go版本是什么( go version
)?
$ go version
go version devel go1.19-db875f4d1b Mon May 16 16:15:58 2022 +0000 darwin/arm64
$ go1.18 version
go version go1.18.2 darwin/arm64
这个问题在最新版本的Go中是否重现?
问题存在于Go 1.18和开发预览版中。
你正在使用什么操作系统和处理器架构( go env
)?
go env
输出
$ go env
GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/rhys/Library/Caches/go-build"
GOENV="/Users/rhys/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/rhys/go/pkg/mod"
GONOPROXY="*"
GONOSUMDB="*"
GOOS="darwin"
GOPATH="/Users/rhys/go"
GOPRIVATE="*"
GOPROXY="direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="devel go1.19-db875f4d1b Mon May 16 16:15:58 2022 +0000"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/lf/n4ry0qv5639_3z0nhyhy3z7c0000gs/T/go-build743809376=/tmp/go-build -gno-record-gcc-switches -fno-common"
你做了什么?
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"os"
"sync"
"sync/atomic"
"time"
)
const duration = 2000 * time.Millisecond
const workers = 3
var stop int32
func main() {
time.AfterFunc(duration, func() { atomic.StoreInt32(&stop, 1) })
var wg sync.WaitGroup
wg.Add(1)
go func() { wg.Done() }()
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
wg.Done()
churn()
}()
}
wg.Wait()
if atomic.LoadInt32(&stop) == 1 {
fmt.Printf("PROBLEM: first goroutine did not get a chance to run\n")
os.Exit(1)
}
fmt.Printf("ok\n")
}
func churn() {
if atomic.LoadInt32(&stop) == 0 {
go churn()
}
}
你期望看到什么?
我期望程序中的每个goroutine都能在几个10毫秒的调度间隔内有机会运行。结果将是两个wg.Done调用都执行,wg.Wait调用完成,并且程序能够快速无错误地退出。
你看到了什么?
当我用GOMAXPROCS=1运行测试程序时,新的goroutine启动使运行时保持忙碌,因此它没有给所有现有的goroutines一个机会运行。除了调用wg.Done之外什么都不做的goroutine直到计时器到期停止新goroutine的启动,然后在新goroutine启动2000毫秒后才有机会运行。
这不仅仅局限于GOMAXPROCS=1,对 go churn()
的其他调用也可以使其他调度器线程保持忙碌。
这是从 #52916 中的原始报告中提炼出来的。CC @golang/runtime
3条答案
按热度按时间wwtsj6pe1#
https://go.dev/cl/406654提到了这个问题:
runtime/pprof: slow new goroutine launches in test
nvbavucw2#
运行时确实会给新创建的goroutine一些优先级,因为
runtime.newproc
将新创建的goroutine放入runnext
槽中,而runtime.findRunnable
(通常)更倾向于本地运行队列(其中runnext
位于顶部)而不是全局运行队列。也许我们在这方面有点过于激进。runnext
确实使新创建的goroutine与父goroutine共享相同的时间片(参见inheritTime
here),但我怀疑churn
运行的时间太短,以至于sysmon
试图抢占它的尝试通常是不成功的[1]。[1]我认为要想成功,它需要在
churn
开始执行后抢占,但在其到达runtime.newproc
的主体之前,这在现实中可能只有十几条指令。kd3sttzy3#
这在Go 1.22中仍然很常见,通常发生在我使用GOMAXPROCS = 1运行go test命令时。