重新插入导致死锁的频道

时间:2019-05-10 11:56:58

标签: go

我有稳定的入站“工作”流,我将其输入到无缓冲通道中。我有一个for range循环来遍历项目并处理它们。如果处理该项目失败,我会将其重新插入到通道中,以便稍后再试。

问题是当我将项目重新插入到通道中时,它会死锁。我知道为什么会这样:处理器在尝试发送时没有从通道读取数据,因此发送永远阻塞。但是我想不出一种解决问题的模式。有人可以协助寻找解决方案吗?

以下是显示我的问题(https://play.golang.org/p/N_-jWL5aOCo)的简单示例代码:

package main

import (
    "fmt"
    "time"
)

type Job struct {
    ID       int
    Attempts int
}

func main() {
    ch := make(chan *Job)
    go fetchJobs(ch)

    for job := range ch {
        if success := processJob(job); !success {
            ch <- job
        }
    }
}

func processJob(job *Job) bool {
    job.Attempts++
    fmt.Printf("Processing job %+v\n", job)

    // Simulate work.
    time.Sleep(time.Millisecond * 500)

    // Simulate failure on some jobs (IDs 10 to 19, 30 to 39, etc.)
    if job.ID%20 >= 10 && job.Attempts == 1 {
        return false
    }

    return true
}

func fetchJobs(ch chan *Job) {
    for i := 0; ; i++ {
        ch <- &Job{ID: i}
    }
}

2 个答案:

答案 0 :(得分:4)

最简单的解决方案是使用新的goroutine将其放回原处:

if success := processJob(job); !success {
    go func() { ch <- job }()
}

如果要避免为此使用新的goroutine,则另一种解决方案是对失败的作业进行“存储”。最简单的存储可能是切片。如果作业处理失败,请将作业追加到失败的作业中。

生产者在获取新作业之前(或之后,取决于您要对失败的作业重新排队的“速度”如何)可以检查是否存在失败的作业,如果存在,则将其中的一些(或全部)入队。当然,必须同步访问此失败的作业存储。

还请注意,您不应无条件重新排队失败的作业,因为如果错误是永久性的,则它们将永远无法完成,从而有可能阻塞整个系统。一个简单的解决方法是仅在其重试计数器小于限制时才重新排队。

尽管如果您有一个没有缓冲的工作通道,并且只有一个生产者和消费者,但是重新排队可能是不必要的麻烦。您也可以对失败的作业重试几次,如果在一定的重试或时间限制内无法成功,则将其视为可撤消的处理。

答案 1 :(得分:-1)

在此示例中存在无限循环,毕竟内存不足