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