go 的 for range 的坑

简介: 在使用 for range 时会出现让人意想不到的结果,比如输出的结果并非是每次的更新结果。到底是什么原因,可以详细看下其中的内容。

corn 调度

本来想开发一个任务调度的服务,目标能够定时去运行一些任务。于是愉快的使用了 corn 包,并根据官网提供的 Demo 编写了自己的调度任务。

func main(){
   c := cron.New(cron.WithSeconds())
   c.AddFunc("*/5 * * * * *", func() {
       fmt.Printf("我是任务1, time =%d\n",time.Now().Unix())
   })
   c.AddFunc("*/1 * * * * *", func() {
        fmt.Printf("我是任务2)
   })
   c.Start()
   select{}
}


调用 corn 对象的 AddFunc 方法就可以添加定时任务,分别设置了任务1每5秒执行一次,任务2每1秒执行一次。就这样很正常执行出结果。

动态加入调度

当我有很多个定时任务时,总不可能是有一个写一个 AddFunc 方法。所以可以使用反射方法,反射得到某个结构体的的方法,然后调用该方法。至于每个定时任务何时触发,可以使用 map 做一个定时任务名和定时时间的做 key-value 对应。

package task
//实现一个含有很多定时任务的结构体
type Task struct {}
func (Task) SyncTask1() {
 log.Printf("我是任务1 time = %d\n", time.Now().Unix())
}
func (Task) SyncTask2() {
 log.Printf("我是任务2")
}
package main
func main(){
var syncList = map[string]string{
  "SyncTask1": "*/5 * * * * *",
  "SyncTask2": "*/1 * * * * *",
 }
 funcs := reflect.ValueOf(&Task.Task{})
 c := cron.New(cron.WithSeconds())
 for key, val := range syncList {
  c.AddFunc(val, func() {
   f := funcs.MethodByName(key)
   f.Call(nil)
  })
  c.Start()
  time.Sleep(time.Second * 3)
 }
 select {}
}


本开开心心的执行以上代码,但是结果没有得到像图1上面的结果而是出现只调度执行任务2。

为什么只有任务2 执行

一开始我以为因为 for 循环这样动态加入定时任务 AddFunc 的原因导致其只执行一次,于是找了官网查看该方法说明,没有说需要什么参数去控制调度,只要调用 AddFunc 方法就会向管理器中加入定时任务,而后所有的定时任务就会依次执行。

于是将问题转移到 for range 循环上,在循环中打印出结果,打印时每次都能打印出值,没什么问题,但是如果打印 value 的内存地址时会发现 2次循环的内存地址都是一样。我增加内容,无论多少个值,value 的内存地址都是一样。

var arr = []string{"hi", "name", "asas", "sasa", "ffd"}
 for key, value := range arr {
  fmt.Println(key, value)
  fmt.Println(&key, &value)
 }


执行结果说明了,在 for 循环中其创建的变量是共享同一块内存地址。所以每次 key 、value 的内存地址都是一样的。如果 for 循环内的业务逻辑是同步的,就不会有什么影响,如果是异步的就会有影响。cron 每次创建一个定时任务都会创建一个新的 goroutine 来执行。这样就导致定时任务触发时访问到 value 都是 for 循环最后一次的值。

解决方案

for 循环中创建变量共享内存,那只需要每次循环时都创建一个新的变量,就可以很好的解决该问题。

for key, value := range arr {
 tempKey, tempValue := key, value
    ...
}

感觉很多面试公司在笔试中很喜欢出这种问题的面试题,如果没有了解到,很可能就会导致错误。

目录
相关文章
|
7月前
|
Cloud Native Go 索引
GO 的 range 如何使用?
GO 的 range 如何使用?
|
8月前
|
Go 索引
案例实战:Go语言for range遍历经典应用场景
案例实战:Go语言for range遍历经典应用场景
60 0
|
8月前
|
安全 Go 索引
Go切片循环就用range 有这一篇就够了
Go切片循环就用range 有这一篇就够了
129 0
|
19天前
|
存储 安全 编译器
掌握Go语言:精通Go语言范围(range),高级应用及进销存系统实战(25)
掌握Go语言:精通Go语言范围(range),高级应用及进销存系统实战(25)
|
11月前
|
Go 索引 Cloud Native
GO 的 range 如何使用?
GO 语言的 for…range 能做什么呢? for…range 如何使用 ?
GO 的 range 如何使用?
|
缓存 Go
Go --- for range会使通道中的缓存值被取出
Go --- for range会使通道中的缓存值被取出
Go --- for range会使通道中的缓存值被取出
|
存储 Go 索引
三分钟学 Go 语言——range深度解析
三分钟学 Go 语言——range深度解析
三分钟学 Go 语言——range深度解析
|
Go C++
Go-分支和循环总结(if、else、switch、for、range、continue、break等)
Go-分支和循环总结(if、else、switch、for、range、continue、break等)
90 0
Go-分支和循环总结(if、else、switch、for、range、continue、break等)
|
存储 编译器 Go
Go语言, range 实现原理
range 是 Go 语言用来遍历的一种方式,它可以操作数组、切片、map、channel 等。
110 0
Go语言, range 实现原理
|
Java 索引
Go+ for range遍历
我们如果使用for遍历的话总觉得代码有点臃肿,不太雅观,这个时候我们可以使用for range来遍历,我们常用它来遍历数组、切片、字符串、map、以及channel。
120 0