Concurrency 潜在递归任务的工作池(即,每个作业可以对其他作业排队)

Concurrency 潜在递归任务的工作池(即,每个作业可以对其他作业排队),concurrency,go,synchronization,worker,Concurrency,Go,Synchronization,Worker,我正在编写一个应用程序,用户可以从许多“作业”(实际上是URL)开始。在开始时(主例程),我将这些URL添加到队列中,然后启动处理这些URL的x goroutine 在特殊情况下,URL指向的资源可能包含更多必须添加到队列中的URL。这3名工人正在等待新工作的到来并处理它们。问题是:一旦每个工人都在等待一份工作(而且没有人在生产),工人们就应该完全停止工作。所以要么所有人都工作,要么没有人工作 我当前的实现看起来像这样,我认为它并不优雅。不幸的是,我想不出一个更好的方法不包括种族条件,我也不完全

我正在编写一个应用程序,用户可以从许多“作业”(实际上是URL)开始。在开始时(主例程),我将这些URL添加到队列中,然后启动处理这些URL的x goroutine

在特殊情况下,URL指向的资源可能包含更多必须添加到队列中的URL。这3名工人正在等待新工作的到来并处理它们。问题是:一旦每个工人都在等待一份工作(而且没有人在生产),工人们就应该完全停止工作。所以要么所有人都工作,要么没有人工作

我当前的实现看起来像这样,我认为它并不优雅。不幸的是,我想不出一个更好的方法不包括种族条件,我也不完全确定这个实现是否真的能按预期工作:

var queue // from somewhere
const WORKER_COUNT = 3
var done chan struct{}

func work(working chan int) {
  absent := make(chan struct{}, 1)
  // if x>1 jobs in sequence are popped, send to "absent" channel only 1 struct.
  // This implementation also assumes that the select statement will be evaluated "in-order" (channel 2 only if channel 1 yields nothing) - is this actually correct? EDIT: It is, according to the specs.
  one := false
  for {
    select {
    case u, ok := <-queue.Pop():
      if !ok {
        close(absent)
        return
      }
      if !one {
        // I have started working (delta + 1)
        working <- 1
        absent <- struct{}{}
        one = true
      }
      // do work with u (which may lead to queue.Push(urls...))
    case <-absent: // no jobs at the moment. consume absent => wait
      one = false
      working <- -1
    }
  }
}

func Start() {
  working := make(chan int)
  for i := 0; i < WORKER_COUNT; i++ {
    go work(working)
  }
  // the amount of actually working workers...
  sum := 0
  for {
    delta := <-working
    sum += delta
    if sum == 0 {
      queue.Close() // close channel -> kill workers.
      done <- struct{}{}
      return
    }
  }
}
var queue//来自某处
const WORKER\u COUNT=3
var done chan结构{}
func工作(工作通道内部){
缺席:=make(chan结构{},1)
//如果按顺序弹出x>1个作业,则只发送到“缺席”通道1结构。
//此实现还假设select语句将按“顺序”进行计算(仅当通道1不产生任何结果时才计算通道2)-这实际上正确吗?编辑:根据规范,正确。
一:=假
为了{
挑选{
案例u,ok:=您可以(参见)控制工作进程的生命周期,并使用非阻塞发送,以便工作进程在尝试排队等待更多作业时不会死锁:

package main

import "sync"

const workers = 4

type job struct{}

func (j *job) do(enqueue func(job)) {
    // do the job, calling enqueue() for subtasks as needed
}

func main() {
    jobs, wg := make(chan job), new(sync.WaitGroup)
    var enqueue func(job)

    // workers
    for i := 0; i < workers; i++ {
        go func() {
            for j := range jobs {
                j.do(enqueue)
                wg.Done()
            }
        }()
    }

    // how to queue a job
    enqueue = func(j job) {
        wg.Add(1)
        select {
        case jobs <- j: // another worker took it
        default: // no free worker; do the job now
            j.do(enqueue)
            wg.Done()
        }
    }

    todo := make([]job, 1000)
    for _, j := range todo {
        enqueue(j)
    }
    wg.Wait()
    close(jobs)
}
主程序包
导入“同步”
施工工人=4
类型作业结构{}
func(j*作业)do(排队func(作业)){
//执行此作业,根据需要为子任务调用enqueue()
}
func main(){
作业,wg:=make(chan作业),new(sync.WaitGroup)
变量排队函数(作业)
//工人
对于i:=0;icase jobs简短版:你有这个想法,但是
sync.WaitGroup
是处理
working
频道簿记的一种更简单的方法。好吧,我真的以为在某个地方读到过,在主线程等待后添加到WaitGroup是一个坏主意。在阅读文档时,我想我误读了…收到。发布回答中的
sync.WaitGroup
示例(以及我必须执行此操作的代码链接),供可能遇到此问题的任何人使用。要求更具体:工人总数不得超过
WORKER\u COUNT
(选项是让
WORKER\u计数-1
workers工作+主例程。另外,想象每个作业都消失了(没有剩余的作业),而当前正在处理的最后一个作业将产生另外100个作业,您的解决方案是否适用于此?意思是:
WORKER\u COUNT
workers将在接下来的100个作业中工作,还是将该作业减少到1个WORKER?是的,新版本可以处理所有这些,请检查下面的内容--它正好开始4个WORKER,当一个WORKERrker无法将一个子任务卸载到另一个子任务,它会立即(即同步)完成。谢谢!这正是我所寻找的。在考虑了您的解决方案后,我将其包含到了我的解决方案中。我实际上有点“幸运”,我使用的队列是我自己编写的包装器,这样我就可以将WaitGroup集成到所述队列中。任何“工作者”从队列中弹出的东西负责调用“Done()”,每当队列中添加某个内容时,队列将wg增加1。这个解决方案好得多。下面是我的代码。非常感谢!