Concurrency 理解goroutines

Concurrency 理解goroutines,concurrency,go,race-condition,Concurrency,Go,Race Condition,我试图理解Go中的并发性。特别是,我编写了这个线程不安全程序: 主程序包 输入“fmt” 变量x=1 func inc_x(){//test 为了{ x+=1 } } func main(){ go公司 为了{ 格式打印LN(x) } } 我认识到我应该使用通道来防止与x竞争,但这不是重点。程序打印1,然后似乎永远循环(不再打印任何内容)。我希望它打印一个无限的数字列表,可能会由于竞争条件而跳过某些数字并重复其他数字(或者更糟的是,在inc\u x中更新数字时打印数字) 我的问题是:为什么程序

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

主程序包
输入“fmt”
变量x=1
func inc_x(){//test
为了{
x+=1
}
}
func main(){
go公司
为了{
格式打印LN(x)
}
}
我认识到我应该使用通道来防止与
x
竞争,但这不是重点。程序打印
1
,然后似乎永远循环(不再打印任何内容)。我希望它打印一个无限的数字列表,可能会由于竞争条件而跳过某些数字并重复其他数字(或者更糟的是,在
inc\u x
中更新数字时打印数字)

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

只是想澄清一下:我不是故意在这个玩具示例中使用频道。

不确定,但我认为
inc\u x
占用了CPU。因为没有IO,所以它不会释放控制

我发现两件事解决了这个问题。一个是在程序开始时调用
runtime.GOMAXPROCS(2)
,然后它就可以工作了,因为现在有两个线程服务于gorouting。另一种方法是插入
time.Sleep(1)
在根据and递增
x
之后,在CPU绑定的Goroutine期间无法调用某些调用(如果Goroutine从未向调度程序屈服)。如果需要阻止主线程,这可能会导致其他goroutine挂起(例如
fmt.Println()
使用的
write()
系统调用)

我发现的解决方案涉及调用cpu绑定的线程以返回调度程序,如下所示:

主程序包
进口(
“fmt”
“运行时”
)
变量x=1
func公司{
为了{
x+=1
runtime.Gosched()
}
}
func main(){
go公司
为了{
格式打印LN(x)
}
}
因为您在Goroutine中只执行一个操作,所以经常调用
runtime.Gosched()
。在init上调用
runtime.GOMAXPROCS(2)
的速度要快一个数量级,但如果您执行的是比递增数字更复杂的操作(例如,处理数组、结构、映射等),则会非常不安全

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


更新:从Go 1.2开始,

关于Go,有几点需要记住:

它们不是java或C++线程意义上的线程
  • goroutines更像是
  • go运行时在系统线程之间多路传输Goroutine
    • 系统线程的数量由环境变量
      GOMAXPROCS
      控制,我认为目前默认为1。这在将来可能会改变
  • goroutine返回其当前线程的方式由几个不同的构造控制
    • 可以将控制权交还给线程
    • 发送一个线程可以将控件返回到线程
    • 执行IO操作可以将控制权交还给线程
    • 显式地将控制权返回给线程

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

    这是两件事情的交互。一个是默认情况下,Go只使用一个内核,另一个是Go必须协同调度goroutines。你的函数inc_x不能产生,因此它垄断了正在使用的单核。解除这两种情况中的任何一种都会产生预期的输出

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


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

    正确地说,如果正确地使用通道,这个问题永远不会发生吗?否则,Go似乎是一个主要问题——如果一个线程占用CPU,而没有其他线程执行。如果有多个CPU,还可以将环境变量设置为GOMAXPROCS=2,然后goroutine可以在主函数之外的单独线程中运行。函数的作用是:告诉运行时在for循环中让步。@user793587取决于“正确”的含义。在紧密循环中轮询可能占用线程,但代码无论如何都是不好的。实际上,这不是一个问题。在很少的情况下,您需要在紧密循环中轮询,您可以显式地向调度器屈服,但它通常只在玩具示例中出现。我听说有计划切换到抢占式调度程序,但当前的协作式调度程序在大多数情况下都能正常工作。您可以在一个处理器上同时运行多个线程/进程,因此“仅当您的处理器有多个核时”是误导性的。我想我会提到,从Go 1.2开始,可以定期在函数输入时调用调度程序。我不认为它可以解决这种情况,但当您有一个调用非内联函数的紧密循环时,它会有所帮助。