go - 遍历go例程时数组索引错误

标签 go goroutine

我在一个循环中为两个函数运行 go routines,使用 sync 等待 go routines 完成,然后在循环外运行一个普通函数,如:

func fetchStudentsAndTeachers(db *sqlx.DB, token string) {
    var students Students
    var teachers Teachers
    wg := &sync.WaitGroup{}
    // defer wg.Wait()
    tch := make(chan Teachers)
    schoolList := fetchActiveOrganization(DB)
    std := make(chan Students)
    for key, value := range schoolList {
        value2 := value
        fmt.Println(key, ":", value)
        wg.Add(1)
        go func() {
            defer wg.Done()
            std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
        }()
        wg.Add(1)
        go func() {
            defer wg.Done()
            tch <- fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
        }()
        students = <-std
        // teachers = <-tch
    }
    wg.Wait() // It panics after this point
    UpdateOrganizationsAndUsers(DB)
    close(std)
    close(tch)
    fmt.Println(students)
    fmt.Println(teachers)
}

现在的问题是,当我退出循环时,会出现索引超出范围的错误。在从 wg.Wait() 转发调试器的控制后,我检查了是否在 wg.Wait() 上使用了 delve 调试器。它 panic 地说:

panic: runtime error: index out of range

(已编辑) 注意:此问题是由于循环迭代一次并运行处理数据库的例程。但是不知何故,循环在例程完成之前再次迭代,这导致了错误。我应该怎么做才能在下一次迭代之前完成这两个例程。

最佳答案

如果您希望 2 个 worker goroutine 在下一次迭代开始之前完成,只需将 wg.Wait() 调用移动到循环体的末尾即可:

for key, value := range schoolList {
    value2 := value
    fmt.Println(key, ":", value)
    wg.Add(1)
    go func() {
        defer wg.Done()
        std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
    }()
    wg.Add(1)
    go func() {
        defer wg.Done()
        tch <- fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
    }()
    students = <-std
    teachers = <-tch
    wg.Wait()
}

另请注意,如果您已经在使用 channel 来传递 goroutine 结果,并且如果没有其他人使用 stdtch channel ,WaitGroup 甚至不需要:

for key, value := range schoolList {
    value2 := value
    fmt.Println(key, ":", value)
    go func() {
        std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
    }()
    go func() {
        tch <- fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
    }()
    students = <-std
    teachers = <-tch
}

这就足够了,因为下一次迭代只有在从 stdtch 都完成接收时才能开始,但是只有在 worker goroutines 完成它们的工作并且在这些 channel 上发送结果。

现在,如果我们考虑一下它的作用:循环的 goroutine 等待 2 个 worker goroutine 完成它们的工作,然后继续(到下一次迭代)。当 2 个 worker 工作时,它只是等待。

我们可以通过在循环的 goroutine 中完成 1 个 worker 的工作来简化和改进它,完成后,等待单个 worker 也完成(如果尚未完成)。

它可能是这样的:

for key, value := range schoolList {
    value2 := value
    fmt.Println(key, ":", value)
    go func() {
        std <- fetchStudentsFromSchool(wg, value2.CleverSchoolID, token)
    }()
    teachers = fetchTeachersFromSchool(wg, value2.CleverSchoolID, token)
    students = <-std
}

我们只是在循环的 goroutine 中获取教师,并且只在并发 goroutine 中获取学生。这具有相同的效果(同时获取学生和教师),开销更少,代码更清晰。

另请注意,由于您现在具有同步功能,可以在工作人员完成之前不开始下一次迭代,因此您无需复制循环变量:它在工作人员的生命周期内不会被修改。所以你可以简单地使用:

for key, value := range schoolList {
    fmt.Println(key, ":", value)
    go func() {
        std <- fetchStudentsFromSchool(wg, value.CleverSchoolID, token)
    }()
    teachers = fetchTeachersFromSchool(wg, value.CleverSchoolID, token)
    students = <-std
}

(这也适用于 WaitGroup 的解决方案。)

关于go - 遍历go例程时数组索引错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50270652/

相关文章:

ajax - 在返回之前如何合并两个 gorountines 的结果?

go - 使用 channel 或互斥体模拟 radio 通信

docker - 容器化应用程序执行本地脚本?

postgresql - Golang postgres 提交未知命令错误?

c++ - 迁移代码库时,哪种下一代低级语言是最好的选择?

Go range over channel 死锁问题,我应该关闭 channel 吗?

performance - 并行版本比golang中的串行版本慢得多

go - 为什么这个 go 例程在关闭阻塞读取连接时随机无法退出?

循环中的 bufio ReadString 是无限的

Golang,重新启动 panic 例程的正确方法