了解goroutines

时间:2012-04-10 20:10:51

标签: concurrency go race-condition

我正在尝试理解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 个答案:

答案 0 :(得分:35)

关于Go的goroutines,有几点要记住。

  1. 它们不是Java或C ++线程意义上的线程。
    1. 他们更喜欢greenlets。
  2. go运行时在系统线程中复用goroutine
    1. 系统线程的数量由环境变量GOMAXPROCS控制,目前我认为默认为1。这可能在将来发生变化。
  3. goroutines回退到当前线程的方式由几种不同的构造控制。
    1. select语句可以将控制权交还给线程。
    2. 发送频道可以控制回线程。
    3. 执行IO操作可以将控制权交还给线程。
    4. runtime.Gosched()显式地将控制权交还给线程。
  4. 您看到的行为是因为main函数永远不会返回到线程,而是涉及一个繁忙的循环,因为只有一个线程,主循环没有地方可以运行。

答案 1 :(得分:17)

根据thisthis,在CPU绑定的Goroutine期间无法调用某些调用(如果Goroutine永远不会产生调度程序)。如果需要阻塞主线程,这会导致其他Goroutines挂起(write()使用的fmt.Println()系统调用就是这种情况)

我发现的解决方案涉及在cpu绑定线程中调用runtime.Gosched()以回退到调度程序,如下所示:

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()经常被称为非常。在init上调用runtime.GOMAXPROCS(2)的速度提高了一个数量级,但如果你做的事情比递增一个数字更复杂(例如,处理数组,结构,映射等),那么它将非常不安全。 / p>

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

更新:从Go 1.2开始,any non-inlined function call can invoke the scheduler.

答案 2 :(得分:7)

这是两件事的互动。一,默认情况下,Go只使用一个核心,而两个Go必须合作安排goroutines。您的函数inc_x不会产生,因此它会垄断正在使用的单个核心。减轻这些条件之一将导致您期望的输出。

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

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

答案 3 :(得分:2)

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

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