Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go 低版本存在的循环问题 #26

Open
RedCrazyGhost opened this issue Jul 20, 2024 · 0 comments
Open

Go 低版本存在的循环问题 #26

RedCrazyGhost opened this issue Jul 20, 2024 · 0 comments
Labels
Golang fa-brands fa-golang

Comments

@RedCrazyGhost
Copy link
Owner

RedCrazyGhost commented Jul 20, 2024

起因

在使用 ginkgo 编写测试用例时,出现了循环只检测最后一个 test case 的情况,起初这让人十分疑惑,这并不符合认知,但确实是出现了这样的问题。

package main

import (
    "context"
    "fmt"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
)

type TestCase struct {
    ...
}

var _ = Describe("test parse services", func() {
  cases := []TestCase{
         {...},
         {...},
         {...},    <<<--- only check this
  }
  for _, c := range cases {
  Context(c.name, func() {
          It("should return the expected results for "+c.name, func() {
                /// checking 
        })
  })
}

解决问题

查阅 ginkgo 的文档发现 dynamically-generating-specs 中有提到循环中出现的问题,主要是把循环变量的副本赋值给局部变量,否则无法在闭包中捕获变化的循环变量

This will generate several Its - one for each category. Note that you must assign a copy of the loop variable to a local variable (that's what category := category is doing) - otherwise the It closure will capture the mutating loop variable and all the specs will run against the last element in the loop. It is idiomatic to give the local copy the same name as the loop variable.

由此可以想到两种方案方案解决这个循环问题

for _, c := range cases {
+ c := c
  Context(c.name, func() {
          It("should return the expected results for "+c.name, func() {
                /// checking 
        })
  })
for _, c := range cases {
+ c := c
  Context(c.name, func() {
          It("should return the expected results for "+c.name, func() {
                /// checking 
        })
  })

后续

网上冲浪的时候,无意间看到 Go 官方 Blog 中有一篇 Fixing For Loops in Go 1.22 的文章,这不就是我在修复 test 中遇到问题嘛,点进去好好研读一番

循环问题起初出认为是 goroutine 并发导致的混淆问题

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

随着时间推移还出现在不使用 goroutine 的情况下

func main() {
    var prints []func()
    for i := 1; i <= 3; i++ {
        prints = append(prints, func() { fmt.Println(i) })
    }
    for _, print := range prints {
        print()
    }
}

人们才开始注意到这个问题不符合常人的认知

能力小测验:下面两个代码片段哪一个是正确修复,哪一个又是无效修复?

for _, informer := range c.informerMap {
+  informer := informer
   go informer.Run(stopCh)
}
for _, a := range alarms {
+  a := a
   go a.Monitor(b)
}

官方修复

Golang 1.22 及其之后的版本使用全新的语义,Golang 1.21 还可以通过 GOEXPERIMENT=loopvar 在测试或者构建中使用新循环语义

GOEXPERIMENT=loopvar go test

Golang 版本低于 1.21 的版本将无法得到新语义的支持

参考

Go Blog Fixing For Loops in Go 1.22
Proposal: Less Error-Prone Loop Variable Scoping
Go Wiki: LoopvarExperiment

@RedCrazyGhost RedCrazyGhost added the Golang fa-brands fa-golang label Jul 20, 2024
@RedCrazyGhost RedCrazyGhost changed the title 循环在 Go 中存在的问题 Go 低版本存在的循环问题 Jul 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Golang fa-brands fa-golang
Projects
None yet
Development

No branches or pull requests

1 participant