运行时:新的goroutine启动可能会无限期地饿死其他工作,

nhjlsmyf  于 5个月前  发布在  Go
关注(0)|答案(3)|浏览(49)

你使用的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

wwtsj6pe

wwtsj6pe1#

https://go.dev/cl/406654提到了这个问题:runtime/pprof: slow new goroutine launches in test

nvbavucw

nvbavucw2#

运行时确实会给新创建的goroutine一些优先级,因为runtime.newproc将新创建的goroutine放入runnext槽中,而runtime.findRunnable(通常)更倾向于本地运行队列(其中runnext位于顶部)而不是全局运行队列。也许我们在这方面有点过于激进。

runnext确实使新创建的goroutine与父goroutine共享相同的时间片(参见inheritTimehere),但我怀疑churn运行的时间太短,以至于sysmon试图抢占它的尝试通常是不成功的[1]。

[1]我认为要想成功,它需要在churn开始执行后抢占,但在其到达runtime.newproc的主体之前,这在现实中可能只有十几条指令。

kd3sttzy

kd3sttzy3#

这在Go 1.22中仍然很常见,通常发生在我使用GOMAXPROCS = 1运行go test命令时。

相关问题