golang中goroutine的执行问题记录

golang中goroutine的执行问题记录

先上代码

func main() {
	runtime.GOMAXPROCS(1)

    number := make(chan int, 10)
	wg := sync.WaitGroup{}
	wg.Add(10)

	for i := 0; i < 10; i++ {
		number <- i
	}

	go func() {
		for i := range number {
			fmt.Println("i:===>", i)
			wg.Done()
		}
	}()

	wg.Wait()
}

运行结果如下

i:===> 0
i:===> 1
i:===> 2
i:===> 3
i:===> 4
i:===> 5
i:===> 6
i:===> 7
i:===> 8
i:===> 9

 我们知道,以上这种写法是比较规范的对goroutine和channel的使用,由于是顺序传入channel中,所以另一个goroutine读取number时也是顺序的。由此并不能分析出goroutine的执行顺序。

接下来,上代码

func main() {
	runtime.GOMAXPROCS(1)

	number := make(chan int, 10)
	wg := sync.WaitGroup{}
	wg.Add(10)

	for i := 0; i < 10; i++ {
		number <- i
	}

	go func() {
		for i := range number {
			fmt.Println("i:===>", i)
			wg.Done()
		}
	}()
	go func() {
		for i := range number {
			fmt.Println("i:+++>", i)
			wg.Done()
		}
	}()

	wg.Wait()
}

运行结果如下

i:+++> 0
i:+++> 1
i:+++> 2
i:+++> 3
i:+++> 4
i:+++> 5
i:+++> 6
i:+++> 7
i:+++> 8
i:+++> 9

 这里相对于代码段1来说,只是增加了另外一个goroutine,执行同样的读取操作。可是执行结果大相径庭。原因就在于goroutine的启用与执行,以及CPU的调度。CPU调度下面再说,先看看goroutine的创建与执行。

goroutine的启用与执行

我们都知道,一个程序必须有一个入口main函数,golang也是如此。但在golang中由于引入了goroutine的概念,所以入口main函数可以看作一个goroutine,通常称为主goroutine。

再来看下面的例子

func main() {
    for i:=0; i<10; i++ {
      go func() {
          fmt.Println("i:", i)
      }()
    }
}

这段代码执行起来绝大部分情况下是不会有任何输出的,当然也不能排除有输出。

原因都知道,因为主routine执行完成for循环就结束了,而对于其他goroutine不论正在执行还是尚未执行都将关闭。

而goroutine之间是公平的竞争关系。

实际上以上代码如果让主routine阻塞等到其他goroutine结束后推出,也是有些问题存在。如下

func main() {
	runtime.GOMAXPROCS(1)
	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println("i:", i)
		}()
	}
	time.Sleep(5 * time.Second)
}

运行结果

i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10
i: 10

 如果启用golang运行时CPU为单核,则会出现上述结果。我想你应该是想连续输出0-9吧。

原因很明显,虽然运行时单核执行,保证了执行顺序。所以调度没问题。

问题就在匿名函数i的数值,为了保证输出可以使用channel来在goroutine中传递数值。或者直接将变量i传入匿名函数。

如下两种

func main() {
	runtime.GOMAXPROCS(10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println("i:", i)
		}(i)
	}
	time.Sleep(5 * time.Second)
}
func main() {
	runtime.GOMAXPROCS(1)
	num := make(chan int, 10)
	for i := 0; i < 10; i++ {
		num <- i
		go func() {
			fmt.Println("i:", <-num)
		}()
	}
	time.Sleep(5 * time.Second)
}

但是细心的你会发现第一种并不是顺序输出0-9,第二种就完美的输出了0-9。原因在哪里呢?

如下第一种输出:

i: 9
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8

这就涉及到goroutine的启用与调度了。由于goroutine之间是公平竞争关系,所以并不能保证goroutine的执行顺序 。

 

个人见解,不一定对,以下未查证,欢迎评论区交流。

goroutine的创建并不是真正的创建,我们称为启用。当执行到一个go语句后,golang运行时系统会试图从某个空闲的goroutine队列中获取,如果当前没有空闲的goroutine时才会创建一个新的goroutine。

知道了这,就能解释为什么9出现在第一个位置,而其他则是顺序输出了。

 

以上都是runtime.GOMAXPROCS(1)运行下

也就是单核运行的结果

 如果没有单核的限制,则会出现各种结果,暂不讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byzf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值