41 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)
}
}
Run Code Online (Sandbox Code Playgroud)
我认识到我应该使用频道来防止竞争条件x,但这不是重点.该程序打印1,然后似乎永远循环(没有打印任何更多).我希望它打印一个无限的数字列表,可能会因为竞争条件而跳过一些并重复其他数字(或者更糟糕的是 - 在更新时打印数字inc_x).
我的问题是:为什么程序只打印一行?
需要明确的是:我没有故意使用这个玩具示例的频道.
Jer*_*all 38
Go的goroutines有几点要记住.
你看到的行为是因为main函数永远不会返回到线程,而是涉及一个繁忙的循环,因为只有一个线程,主循环没有地方可以运行.
lun*_*chs 17
根据这个和这个,一些调用不能在CPU绑定的Goroutine期间调用(如果Goroutine永远不会产生调度程序).如果需要阻塞主线程,这会导致其他Goroutines挂起(例如使用的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)
}
}
Run Code Online (Sandbox Code Playgroud)
因为你只有在够程执行一个操作,runtime.Gosched()是被称为非常频繁.调用runtime.GOMAXPROCS(2)init的速度要快一个数量级,但如果你做的事情比递增一个数字更复杂(例如,处理数组,结构,映射等),那么它将非常不安全.
在这种情况下,最佳实践可能是使用通道来管理对资源的共享访问.
更新:从Go 1.2开始,任何非内联函数调用都可以调用调度程序.
这是两件事的互动.一,默认情况下,Go只使用一个核心,而两个Go必须合作安排goroutines.您的函数inc_x不会产生,因此它会垄断正在使用的单个核心.减轻这些条件中的任何一个都将导致您期望的输出.
说"核心"有点光彩.Go实际上可能在幕后使用多个核心,但是它使用一个名为GOMAXPROCS的变量来确定调度执行非系统任务的goroutine的线程数.如FAQ和Effective Go中所述,默认值为1,但可以使用环境变量或运行时函数将其设置得更高.这可能会提供您期望的输出,但前提是您的处理器具有多个内核.
独立于核心和GOMAXPROCS,您可以为运行时中的goroutine调度程序提供机会来完成它的工作.调度程序不能抢占正在运行的goroutine,但必须等待它返回运行时并请求某些服务,例如IO,time.Sleep()或runtime.Gosched().在inc_x中添加这样的内容会产生预期的输出.运行main()的goroutine已经在使用fmt.Println请求服务,所以现在两个goroutine定期产生运行时,它可以进行某种公平的调度.
| 归档时间: |
|
| 查看次数: |
8858 次 |
| 最近记录: |