首页 > 解决方案 > Go循环轮询案例分支中的并发未命中

问题描述

我在Go中实现了一个非常简单的并发程序。有 2 个通道tododone用于指示完成了哪个任务。有 5 个routines被执行,每个都需要自己的时间来完成。我想每 100 毫秒查看一次正在发生的事情的状态。

但是我尝试过,但投票分支case <-time.After(100 * time.Millisecond):似乎从未被调用过。如果我将时间减少到小于 100 毫秒,它有时会被调用(不是以一种一致的方式)。

我的理解是go func在单独的Go调度程序线程中执行该方法。因此,我不明白为什么case投票从未被击中。我试图在另一个之前/之后移动特定的案例分支,但没有任何改变。

有什么建议么?

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

func concurrent(id int, done chan int, todo chan int) {
    for {
        // doing a task
        t := randInt(50, 100)
        time.Sleep(time.Duration(t) * time.Millisecond)
        done <- id
        // redo again this task
        t = randInt(50, 100)
        time.Sleep(time.Duration(t) * time.Millisecond)
        todo <- id
    }
}

func randInt(min int, max int) int {
    return (min + rand.Intn(max-min))
}

func seedRandom() {
    rand.Seed(time.Now().UTC().UnixNano())
}

func main() {
    seedRandom()

    todo := make(chan int, 5)
    done := make(chan int, 5)

    for i := 0; i < 5; i++ {
        todo <- i
    }

    timeout := make(chan bool)
    go func() {
        time.Sleep(1 * time.Second)
        timeout <- true
    }()

    var mu sync.Mutex
    var output []int

loop:
    for {
        select {
        case <-time.After(100 * time.Millisecond):
            //this branch is never hit?
            fmt.Printf("\nPolling status: %v\n", output)
        case <-timeout:
            fmt.Printf("\nDing ding, time is up!\n")
            break loop
        case id := <-done:
            mu.Lock()
            output = append(output, id)
            fmt.Printf(".") 
            mu.Unlock()
        case id := <-todo:
            go concurrent(id, done, todo)
        }
    }
}

更新遵循我在 Go Playgound 中创建此版本的答案后:https: //play.golang.org/p/f08t984BdPt。这按预期工作

标签: goconcurrency

解决方案


您正在创建 5 个 goroutine(func 并发),并且在您选择的情况下使用 todo 通道,并且该通道正在并发功能中使用,因此您最终创建了很多 goroutine

func concurrent(id int, done chan int, todo chan int) {
    for {
        // doing a task
        t := randInt(50, 100)
        time.Sleep(time.Duration(t) * time.Millisecond)
        done <- id
        // redo again this task
        t = randInt(50, 100)
        time.Sleep(time.Duration(t) * time.Millisecond)
        by doing this call you are re-crating the go-routime
        todo <- id
    }
}

当我运行您的代码时,我得到“runtime.NumGoroutine()”“仍在运行的 goRoutines 数量 347”

当您在 for 循环中实现 time.After(100 * time.Millisecond) 时,它会在每次遇到其他情况时重置,在您的情况下 id := <-todo: && id := <-done: will总是在 100 毫秒内被击中,这就是为什么你没有得到预期的输出(从你的代码现在的情况来看,我会说 go-routines 的数量会成倍增加,并且每个 em 都会等待发送值来完成并且很少在 todo 频道上,所以你的循环不会有足够的时间(100 毫秒)来等待时间。之后)

loop:
for {
    select {
    case <-time.After(100 * time.Millisecond): ->this will always get reset ( we can use time.Ticker as it will create a single object that will signal for each and every 100ms https://golang.org/pkg/time/#NewTicker
        //this branch is never hit?
        fmt.Printf("\nPolling status: %v\n", output)
    case <-timeout:
        fmt.Printf("\nDing ding, time is up!\n")
        break loop
    case id := <-done: -> **this will get called**  
        //the mutex call is actually not very usefull as this only get called once per loop and is prefectly thread safe in this code 
        mu.Lock()
        output = append(output, id)
        fmt.Printf(".") 
        mu.Unlock()
    case id := <-todo: -> **this will get called** 
        go concurrent(id, done, todo)
    }
}

}

https://play.golang.org/p/SmlSIUIF5jn -> 我做了一些修改以使您的代码按预期工作..

尝试参考这个来更好地理解 golang 通道和 goroutine

https://tour.golang.org/concurrency/1


推荐阅读