为了账号安全,请及时绑定邮箱和手机立即绑定

了解goroutines

了解goroutines

Go
HUWWW 2021-04-25 10:21:33
我试图了解Go中的并发性。特别是,我编写了以下线程不安全程序:package mainimport "fmt"var x = 1func inc_x() { //test  for {    x += 1  }}func main() {  go inc_x()  for {    fmt.Println(x)  }}我知道我应该使用渠道来防止与发生竞争x,但这不是重点。该程序打印1,然后似乎永远循环(不再打印任何内容)。我希望它能打印出无限的数字列表,可能由于竞态条件而跳过某些数字并重复其他数字(或更糟糕的是,在更新数字时打印数字inc_x)。我的问题是:为什么程序只打印一行?只是要清楚一点:在这个玩具示例中,我并不是故意使用渠道。
查看完整描述

3 回答

?
慕的地8271018

TA贡献1796条经验 获得超4个赞

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

  1. 从Java或C ++线程的意义上来说,它们不是线程

  2. go运行时在系统线程之间多路复用goroutines

    • 系统线程的数量由环境变量控制,GOMAXPROCS我认为当前默认为1。将来可能会改变

  3. goroutine屈服于其当前线程的方式由几种不同的结构控制

    • select语句可以产生控制回线

    • 通道上发送可以将控制权交还给线程

    • 进行IO操作可以将控制权交还给线程

    • runtime.Gosched() 显式地将控制权交还给线程

您看到的行为是因为main函数从不屈服于线程,而是参与了繁忙的循环,并且因为只有一个线程,所以main循环无处运行。


查看完整回答
反对 回复 2021-05-10
?
阿波罗的战车

TA贡献1862条经验 获得超6个赞

根据这个和这个,几个电话不能CPU密集型够程的过程中调用(如果够程从不屈服于调度)。如果其他Goroutine需要阻塞主线程,这可能会导致它们挂起(例如,所使用的write()syscall就是这种情况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的速度快一个数量级,但是如果您做的事情比增加数字复杂得多(例如,处理数组,结构,映射等),则调用init会非常不安全。


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


查看完整回答
反对 回复 2021-05-10
?
鸿蒙传说

TA贡献1865条经验 获得超7个赞

这是两件事的相互作用。一个默认情况下,Go仅使用单个内核,第二个Go必须协同计划goroutine。您的函数inc_x不会屈服,因此会垄断所使用的单个内核。消除这些条件中的任何一个都将导致您期望的输出。

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

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


查看完整回答
反对 回复 2021-05-10
  • 3 回答
  • 0 关注
  • 224 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信