go - 当设置为具有阻塞操作的单核时,在 Go 中实现并发

标签 go concurrency parallel-processing

我正在测试阻塞操作如何在 Go 上工作,以及它如何剥夺其他 go-routines 共享处理器,所以我做了那个测试:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func test2() {
    for i := 1; ; i++ {
        fmt.Println(i, time.Now())
    }
}

func test() {
    a := 100
    for i := 1; i < 1000; i++ {
        a = i*100/i + a
        fmt.Println("value: " , a )
    }
}

func main() {
    runtime.GOMAXPROCS(1)
    go test2()
    for {
        test()
    }
}

正如您在示例中看到的,main() 的第一行我将 Go 设置为使用单核并且运行一个永无止境的任务,这样它会阻塞任何其他进程,虽然我看到了我没想到的结果,但我发现测试和 test2 都在运行,每个都有它的时间共享(每个进程的时间共享比我设置 时更长) GOMAXPROCS 到更高的值)。这是输出:

https://gist.github.com/anonymous/b3634be74d30fd36f552

我该如何解释?

附言我正在使用 Go version 1.5.3

更新

我通过将 GOMAXPROCS 设置为 2 做了一些更改,从测试函数中删除了 fmt.Println("value: ", a ),现在,程序运行 test2 一段时间,test 函数接管,没有其他运行!

最佳答案

一个重要的区别是 GOMAXPROCS=1 不会将运行时限制为 1 个内核,它会将主动运行用户代码的操作系统线程数限制为 1。所有(gc 派生的)Go 程序都是多线程。

在大多数情况下,繁忙的循环会干扰调度程序和/或垃圾收集,有时即使 GOMAXPROCS > 1 也是如此。不过在您的示例中,对 test()fmt.Println 函数的调用是调度点,它们允许运行时调度程序执行其他 goroutine。


正如您所指出的,如果从 test 中删除对 fmt.Println 的调用,则 test2() 的进度最终会停止(显然,仅调用 test() 不足以让调度程序继续进行。可能是为了快速、内联、被内部 for 循环阻塞,等等)。

在这种情况下,这是因为对 test() 的繁忙循环调用阻止了 GC 执行垃圾收集的停止世界阶段,并阻塞了调度程序。您可以通过使用 GOGC=off 运行程序来验证这一点。

调度、cpu 绑定(bind)任务、垃圾收集等细节可能会因版本而异,但由于 goroutines 是协作调度的,所以忙循环总是错误的。如果您确实需要长时间运行的 cpu 绑定(bind)循环,您可以通过偶尔插入对 runtime.Gosched() 的调用来与调度程序合作。屈服于其他协程。

关于go - 当设置为具有阻塞操作的单核时,在 Go 中实现并发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35509753/

相关文章:

java - JPA 并发问题 "On release of batch it still contained JDBC statements"

java - 为什么这个语句被认为是原子的?

c# - 我应该总是使用 Parallel.Foreach 因为更多的线程必须加速一切吗?

go - 如何检查 golang 二进制文件是否使用 --ldflags ="-s -w"编译

go - 如何使用 bson.SetBSON 或 bson.Raw?

concurrency - 死锁和无限期推迟有什么区别?

c++ - MPI 如何发送和接收未知数据类型

r - Windows 不支持“mc.cores”> 1

go - html 未在电子邮件 golang 中翻译

go - 为什么 strings.Builder 在我的测试程序中追加字符串比 fmt.Sprint 慢?