浅谈golang for 循环中使用协程的问题

 更新时间:2020年12月21日 14:17:53   作者:胖达团长  
这篇文章主要介绍了浅谈golang for 循环中使用协程的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

两个例子

package main 
import (
 "fmt"
 "time"
)
 
func Process1(tasks []string) {
 for _, task := range tasks {
 // 启动协程并发处理任务
 go func() {
 fmt.Printf("Worker start process task: %s\n", task)
 }()
 }
}
 
func main() { 
 tasks := []string{"1", "2", "3", "4", "5"}
 Process1(tasks)
 time.Sleep(2 * time.Second)
}

结果:

第一次运行

Worker start process task: 3
Worker start process task: 4
Worker start process task: 4
Worker start process task: 5
Worker start process task: 5

第二次运行

Worker start process task: 2
Worker start process task: 5
Worker start process task: 5
Worker start process task: 5
Worker start process task: 5
package main 
import (
 "fmt"
 "time"
)
 
func Process1(tasks []string) {
 for _, task := range tasks {
 // 启动协程并发处理任务
 go func() {
 fmt.Printf("Worker start process task: %s\n", task)
 }()
 }
}
 
func Process2(tasks []string) {
 for _, task := range tasks {
 // 启动协程并发处理任务
 go func(t string) {
 fmt.Printf("Worker start process task: %s\n", t)
 }(task)
 }
}
func main() {
 tasks := []string{"1", "2", "3", "4", "5"}
 Process2(tasks)
 time.Sleep(2 * time.Second)
}

结果

第一次运行

Worker start process task: 5
Worker start process task: 4
Worker start process task: 2
Worker start process task: 3
Worker start process task: 1

第二次运行

Worker start process task: 2
Worker start process task: 5
Worker start process task: 4
Worker start process task: 1
Worker start process task: 3

上述问题,有个共同点就是都引用了循环变量。即在for index, value := range xxx语句中,

index和value便是循环变量。不同点是循环变量的使用方式,有的是直接在协程中引用(题目一),有的作为参数传递(题目二)。

循环变量是易变的

首先,循环变量实际上只是一个普通的变量。

语句for index, value := range xxx中,每次循环index和value都会被重新赋值(并非生成新的变量)。

如果循环体中会启动协程(并且协程会使用循环变量),就需要格外注意了,因为很可能循环结束后协程才开始执行,

此时,所有协程使用的循环变量有可能已被改写。(是否会改写取决于引用循环变量的方式)

循环变量需要绑定

在题目一中,协程函数体中引用了循环变量task,协程从被创建到被调度执行期间循环变量极有可能被改写,所以会出现两次结果相差较大,比如第一个协程启动for range变量正好循环到3,for属于主协程的一部分。go func是子协程,主子分开看。这种情况下,其实for range里面的循环变量没有跟子协程绑定,称之为变量没有绑定。所以,题目一打印结果是混乱的。很有可能(随机)所有协程执行的task都是列表中的最后一个task,也可能不是。

在题目二中,协程函数体中并没有直接引用循环变量task,而是使用的参数与协程进行了绑定。而在创建协程时,循环变量task

作为函数参数传递给了协程。参数传递的过程实际上也生成了新的变量,也即间接完成了绑定。

所以,题目二实际上是没有问题的。就是实际参数顺序是按照for range产生的变量顺序绑定给子协程的。

ps:

简单点来说

如果循环体没有并发出现,则引用循环变量一般不会出现问题;

如果循环体有并发,则根据引用循环变量的位置不同而有所区别

通过参数完成绑定,则一般没有问题;

函数体中引用,则需要显式地绑定

补充:Go语言的协程中,写死循环的注意点:

现象:

在写Go的多协程程序时,出现过几次无法理解的情况。

有一次,我想写一个能跑满cpu的程序,最容易想到的就是,开几个Go的协程,每个协程里写死循环。没想到,运行的时候发现,协程就只开出了一个。

另一次,我写了个程序,也是开了多个协程。因为如果不阻塞住主函数,主函数一结束,程序就会结束。所以我就在主函数结束前加了个死循环。然后就发现整个协程都被卡住了。

分析:

其实,这个东西是协程的特点。以前没用过协程,加上Go又说可以当线程用。所以想当然的写了死循环。

准确的说,是在Go语言里,写了死循环,并且死循环内并没有什么系统调用,只有简单的计算这类的。你就会发现,Go的协程调度就废掉了。

协程并非像线程那样,是由CPU中断来触发切换的。它不是应用程序能控制的(操作系统内核的某些关键操作会被保护,不被中断)。即使你在线程里写了死循环,只要周期一到,CPU产生终端,死循环会被打断,重新调度。但是,协程就不是这样了,协程的调度其实是在协程调用了某个系统调用时,自动跳到另一个协程执行。也就是这个“中断”是程序主动产生的,而不是被”中断”。

所以,协程中,如果你写了死循环,那你的死循环就会一直跑着,而不会让别的协程运行。主函数中也是一样,而且主函数中执行这个会让整个协程卡住,因为调度的代码没法被执行。

在Go语言中,如果你想写死循环,循环里面没有系统调用,又想让Go的协程能起作用,只需要在死循环里面加一条语句即可。估计系统调用时也是这个语句起的作用。

runtime.Gosched() //主动让出时间片

还可以使用

select{}

来实现无限阻塞,而不是使用for{}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。如有错误或未考虑完全的地方,望不吝赐教。

相关文章

  • Golang 中的 条件变量(sync.Cond)详解

    Golang 中的 条件变量(sync.Cond)详解

    这篇文章主要介绍了Golang 中的 条件变量(sync.Cond)详解的相关资料,需要的朋友可以参考下
    2022-12-12
  • go单体日志采集zincsearch方案实现

    go单体日志采集zincsearch方案实现

    这篇文章主要为大家介绍了go单体日志采集zincsearch方案实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • go-zero源码阅读之布隆过滤器实现代码

    go-zero源码阅读之布隆过滤器实现代码

    布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难,这篇文章主要介绍了go-zero源码阅读-布隆过滤器,需要的朋友可以参考下
    2023-02-02
  • golang中"var"与":="的区别解析

    golang中"var"与":="的区别解析

    这篇文章主要介绍了golang中“var”与“:=”的区别,使用var关键字是Go最基本的定义变量方式,有时也会使用到:=来定义变量,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • Golang单元测试与断言编写流程详解

    Golang单元测试与断言编写流程详解

    这篇文章主要介绍了Golang单元测试与断言编写流程,单元测试也是一个很重要的事情。单元测试是指在开发中,对一个函数或模块的测试。其强调的是对单元进行测试
    2022-12-12
  • Go语言排序算法之插入排序与生成随机数详解

    Go语言排序算法之插入排序与生成随机数详解

    从这篇文章开始将带领大家学习Go语言的经典排序算法,比如插入排序、选择排序、冒泡排序、希尔排序、归并排序、堆排序和快排,二分搜索,外部排序和MapReduce等,本文将先详细介绍插入排序,并给大家分享了go语言生成随机数的方法,下面来一起看看吧。
    2017-11-11
  • Go语言编程通过dwarf获取内联函数

    Go语言编程通过dwarf获取内联函数

    这篇文章主要为大家介绍了Go语言编程通过dwarf获取内联函数详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • golang反射机制的用法详解

    golang反射机制的用法详解

    Golang 作为静态类型的编译型语言,虽然在设计上倾向于简洁和高效,但也内置了强大的反射机制,本文将深入讲解 Golang 的反射机制,帮助大家更好地理解和运用这一强大的特性,需要的朋友可以参考下
    2023-12-12
  • 关于go平滑重启库overseer实现原理详解

    关于go平滑重启库overseer实现原理详解

    这篇文章主要为大家详细介绍了关于go平滑重启库overseer实现原理,文中的示例代码讲解详细,具有一定的参考价值,有需要的小伙伴可以参考下
    2023-11-11
  • Go来合并两个csv的实现示例

    Go来合并两个csv的实现示例

    本文主要介绍了Go来合并两个csv的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10

最新评论