41

我试图理解 Go 中的并发性。特别是,我编写了这个线程不安全的程序:

package main

import "fmt"

var x = 1

func inc_x() { //test
  for {
    x += 1
  }
}

func main() {
  go inc_x()
  for {
    fmt.Println(x)
  }
}

我认识到我应该使用通道来防止竞争条件x,但这不是重点。程序打印1然后似乎永远循环(不再打印任何内容)。我希望它打印一个无限的数字列表,可能会由于竞争条件而跳过一些并重复其他的(或者更糟——在更新时打印数字inc_x)。

我的问题是:为什么程序只打印一行?

只是要明确一点:对于这个玩具示例,我不是故意使用频道。

4

4 回答 4

40

关于 Go 的goroutines有几点需要牢记:

  1. 它们不是 Java 或 C++ 线程意义上的线程
  2. Go 运行时跨系统线程多路复用 goroutine
    • 系统线程的数量由环境变量控制,GOMAXPROCS目前我认为默认为 1。这在未来可能会改变
  3. goroutine 回退到当前线程的方式由几个不同的结构控制
    • select 语句可以将控制权交还给线程
    • 在通道上发送可以将控制权交还给线程
    • 执行 IO 操作可以将控制权交还给线程
    • runtime.Gosched()显式将控制权交还给线程

您看到的行为是因为 main 函数永远不会返回给线程,而是参与了一个繁忙的循环,并且由于只有一个线程,所以主循环没有地方运行。

于 2012-04-10T21:24:14.563 回答
18

根据thisthis,一些调用不能在 CPU 绑定的 Goroutine 期间调用(如果 Goroutine 永远不会屈服于调度程序)。如果需要阻塞主线程,这可能会导致其他 Goroutines 挂起(例如write()syscall 使用的情况fmt.Println()

我发现的解决方案涉及调用runtime.Gosched()您的 cpu-bound 线程以返回给调度程序,如下所示:

package main

import (
  "fmt"
  "runtime"
)

var x = 1

func inc_x() {
  for {
    x += 1
    runtime.Gosched()
  }
}

func main() {
  go inc_x()
  for {
    fmt.Println(x)
  }
}

因为您在 Goroutine 中只执行一项操作,runtime.Gosched()所以被非常频繁地调用。调用runtime.GOMAXPROCS(2)init 会快一个数量级,但如果您执行的操作比递增数字更复杂(例如,处理数组、结构、映射等),则会非常不安全。

在这种情况下,最佳实践可能是使用通道来管理对资源的共享访问。

更新:从 Go 1.2 开始,任何非内联函数调用都可以调用调度程序。

于 2012-04-10T20:56:57.010 回答
8

这是两件事的相互作用。一,默认情况下,Go 只使用单核,二,Go 必须协同调度 goroutine。您的函数 inc_x 不会产生,因此它垄断了正在使用的单核。缓解这些条件中的任何一个都会导致您期望的输出。

说“核心”有点夸张。Go 实际上可能在后台使用多个内核,但它使用一个名为 GOMAXPROCS 的变量来确定线程数来调度执行非系统任务的 goroutine。如常见问题解答Effective Go中所述,默认值为 1,但可以使用环境变量或运行时函数将其设置得更高。这可能会提供您期望的输出,但前提是您的处理器具有多个内核。

独立于内核和 GOMAXPROCS,您可以让运行时中的 goroutine 调度程序有机会完成它的工作。调度程序不能抢占正在运行的 goroutine,但必须等待它返回运行时并请求一些服务,例如 IO、time.Sleep() 或 runtime.Gosched()。在 inc_x 中添加类似的内容会产生预期的输出。运行 main() 的 goroutine 已经在使用 fmt.Println 请求服务,因此现在两个 goroutine 现在周期性地屈服于运行时,它可以进行某种公平调度。

于 2012-04-10T23:37:59.077 回答
2

不确定,但我认为inc_x会占用 CPU。由于没有 IO,它不会释放控制权。

我找到了解决这个问题的两件事。一个是runtime.GOMAXPROCS(2)在程序开始时调用,然后它会工作,因为现在有两个线程服务于 goroutings。另一种是time.Sleep(1)递增后插入x

于 2012-04-10T20:37:01.567 回答