我想了解并发围棋。 特别是,我写了这个线程不安全的程序:
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
)。
我的问题是:为什么程序只打印一行?
只是要清楚:我没有使用上的宗旨,为这个玩具例如渠道。
有几件事情要记住关于围棋的够程。
- 他们不是在Java的还是C ++线程的线程感。
- 他们更喜欢greenlets。
- Go运行时整个系统线程复用够程
- 系统线程的数目由环境变量GOMAXPROCS,缺省值为1目前我想控制。 这可能在未来改变。
- 该方法够程产生回到自己当前线程是由几个不同的结构控制。
- SELECT语句可以得到控制回线。
- 发送信道上可以产生控制返回到线程。
- 这样做的IO操作可以得到控制回线。
- runtime.Gosched()显式地将控制权返回到线程。
您所看到的行为是因为主要的功能从来没有得到回线,转而参与繁忙的循环,因为只有一个线程主循环没有地方跑。
根据这个和这个 ,几个电话不能CPU密集型够程的过程中调用(如果够程从不屈服调度)。 这可能会导致其他够程挂起,如果他们需要阻塞主线程(例如与的情况下write()
通过使用系统调用fmt.Println()
该解决方案,我发现参与呼叫runtime.Gosched()
在你的CPU绑定线程产生回调度,如下所示:
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)
}
}
因为你只有在够程,执行一个操作runtime.Gosched()
被经常调用。 调用runtime.GOMAXPROCS(2)
上的init是一个数量级的速度更快,但会非常线程安全的,如果你有什么比增加了一些更复杂(例如,处理数组,结构,地图等)。
在这种情况下,最好的做法潜在地使用信道来管理对资源的共享访问。
更新:由于围棋1.2, 任何非内联函数调用可以调用调度。
这是两件事情的相互作用。 一,在默认情况下,去只使用单核,两,围棋必须协同调度够程。 您的功能inc_x没有产生,所以它独占使用的单核。 缓解这些条件会导致你期望的输出。
说“核心”是有点光泽。 去可以实际使用多个内核在幕后,但它采用了一种名为GOMAXPROCS变量来确定安排你够程被执行非系统任务的线程数。 如在说明的常见问题和有效去的默认值为1,但是也可以使用环境变量或运行时功能设定得较高。 这将有可能给你所期望的输出,但只有当你的处理器具有多个核心。
独立核和GOMAXPROCS的,你可以给在运行时的调度够程有机会做的工作。 调度不能抢占正在运行的goroutine,但必须等待它回来了运行时间和请求某种服务,如IO,time.Sleep(),或runtime.Gosched()。 在inc_x这样添加任何内容产生期望的输出。 运行主要的够程()已经请求与fmt.Println服务,因此与两个够程现在定期屈服于运行时,它可以做某种公平调度的。
不知道,但我认为 inc_x
是霸占CPU。 由于没有IO它不释放控制。
我发现,解决了两件事情。 一个是调用runtime.GOMAXPROCS(2)
在节目的开头,然后它会工作,因为现在有两个线程服务goroutings。 另一种是插入time.Sleep(1)
递增后x
。