一个常见的golang的调度的trap,主要的在使用for循环中生成新的goroutine的时候全局变量或者是局部变量的问题。
一个常见的调度trap
array := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"} |
可以看到输出的结果都是8和i。似乎与我们的初衷不符,最初的意图是index与item每次为1,a;2,b;3,c;….这样,然后每次输出对应的结果。但显然,实际程序的执行流程并非是想我们所想的那样,关键是要明白,go func中的index与item分别表示的是什么,这里的go func每个index与item是共享的,并不是局部的,由于for循环的执行是很快的,每次循环启动一个go routine,在for循环结束之后(此时index与item的值分别变成了8与e),但是这个时候第一个启动的go routine可能还没有开始执行,由于它们是共享变量的,之后所有输出的index与item都是8与e于是出现了上面的效果。
关键问题是将全局的值变成局部的值,即是在每次go routine启动的时候有一个参数声明,比如每次在go routine启动的时候,传进来一个当时这个go routine 被调度到时候的index值,之后这个go routine再被调度到的时候,就能打印出当时的那个值,这样才是这个程序的真正意图,将之前的程序稍微修改下:
length := len(array) |
根据上面的例子,可以看到,实际给go func传递参数的格式如下:
go func(形参名称 形参类型){...}(实参变量名称) |
即使这样,go routine被调度的的先后顺序仍然是没法保证的,最多能做到的也只是在该go routine被调度到的时候,执行的数据正确而已,所以
不要对函数的执行时机做任何假设,除非你确实能够做出让这种假设成为绝对事实的保证。 |
使用runtime.Gosched
还有一种方式,是在执行这个函数的时候,相当于手动地让调度器进行了新的一轮的调度,这样就使得其他的goroutine可以有机会运行。
但是这样并不能100%保证被调度到的goroutine之前并没有被执行过。但是实际情况可能比较复杂,仅仅依靠重新调度这样的策略,可能还是会存在一定的随机性,不一定能满足我们的需求。
当然关于goroutine的使用知识还有很多,这里就是一个基本的注意点。
相关参考
《Go 并发编程实践》