Concurrency Go中惯用的可变大小工作池

Concurrency Go中惯用的可变大小工作池,concurrency,go,semaphore,goroutine,worker-process,Concurrency,Go,Semaphore,Goroutine,Worker Process,我正试图在围棋中实现一批工人。渠道部分中的高效围棋展示了边界资源使用的优秀示例。只需使用与工作池一样大的缓冲区创建一个通道。然后在通道中填充工人,完成后将他们送回通道。接收来自通道块的数据,直到有工作者可用。所以通道和循环是整个实现-非常酷 或者,也可以阻止发送到通道中,但想法相同 我的问题是在工作池运行时更改其大小。我认为没有办法改变频道的大小。我有一些想法,但大多数似乎太复杂了。实际上,使用通道和空结构以几乎相同的方式实现信号量,但在谷歌搜索golang信号量时,这些东西总是出现同样的问题。

我正试图在围棋中实现一批工人。渠道部分中的高效围棋展示了边界资源使用的优秀示例。只需使用与工作池一样大的缓冲区创建一个通道。然后在通道中填充工人,完成后将他们送回通道。接收来自通道块的数据,直到有工作者可用。所以通道和循环是整个实现-非常酷

或者,也可以阻止发送到通道中,但想法相同


我的问题是在工作池运行时更改其大小。我认为没有办法改变频道的大小。我有一些想法,但大多数似乎太复杂了。实际上,使用通道和空结构以几乎相同的方式实现信号量,但在谷歌搜索golang信号量时,这些东西总是出现同样的问题。

一个简单的改变可以认为是拥有一个控制信号量大小的通道。 相关部分是select语句。如果队列中有更多的工作,则使用当前信号量处理它。如果有更改信号量大小的请求,请更改它并使用新信号量继续处理req队列。请注意,旧的将被垃圾收集

package main

import "time"
import "fmt"

type Request struct{ num int }
var quit chan struct{} = make(chan struct{})

func Serve(queue chan *Request, resize chan int, semsize int) {
    for {
        sem := make(chan struct{}, semsize)
        var req *Request
        select {
        case semsize = <-resize:
            {
                sem = make(chan struct{}, semsize)
                fmt.Println("changing semaphore size to ", semsize)
            }
        case req = <-queue:
            {
                sem <- struct{}{}   // Block until there's capacity to process a request.
                go handle(req, sem) // Don't wait for handle to finish.
            }
                case <-quit:
                     return
        }

    }
}

func process(r *Request) {
  fmt.Println("Handled Request", r.num)
}

func handle(r *Request, sem chan struct{}) {
    process(r) // May take a long time & use a lot of memory or CPU
    <-sem      // Done; enable next request to run.
}

func main() {
    workq := make(chan *Request, 1)
    ctrlq := make(chan int)
    go func() {
        for i := 0; i < 20; i += 1 {
            <-time.After(100 * time.Millisecond)
            workq <- &Request{i}
        }
        <-time.After(500 * time.Millisecond)
            quit <- struct{}{}
    }()
    go func() {
        <-time.After(500 * time.Millisecond)
        ctrlq <- 10
    }()
    Serve(workq, ctrlq, 1)
}

我会反过来做。我不会生成许多仍然需要大量内存的goroutine并使用通道来阻止它们,而是将工作人员建模为goroutine并使用通道来分发工作。大概是这样的:

package main

import (
    "fmt"
    "sync"
)

type Task string

func worker(tasks <-chan Task, quit <-chan bool, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case task, ok := <-tasks:
            if !ok {
                return
            }
            fmt.Println("processing task", task)
        case <-quit:
            return
        }
    }
}

func main() {
    tasks := make(chan Task, 128)
    quit := make(chan bool)
    var wg sync.WaitGroup

    // spawn 5 workers
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go worker(tasks, quit, &wg)
    }

    // distribute some tasks
    tasks <- Task("foo")
    tasks <- Task("bar")

    // remove two workers
    quit <- true
    quit <- true

    // add three more workers
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go worker(tasks, quit, &wg)
    }

    // distribute more tasks
    for i := 0; i < 20; i++ {
        tasks <- Task(fmt.Sprintf("additional_%d", i+1))
    }

    // end of tasks. the workers should quit afterwards
    close(tasks)
    // use "close(quit)", if you do not want to wait for the remaining tasks

    // wait for all workers to shut down properly
    wg.Wait()
}
使用一些方便的方法创建一个单独的WorkerPool类型可能是一个好主意。此外,使用还包含done通道的结构(用于表示任务已成功执行)而不是type Task string也是很常见的


编辑:我又玩了一会儿,得出了以下结论:。这基本上是同一个例子,有一个更好的API。

为什么要有一个可变大小的工作池?@fabrizioM工作人员自己做的很少——他们实际上只是控制几个外部进程。工人的数量应取决于机器的负载以及外部流程和其他因素,例如,一种工作类型与另一种工作类型的优先级。在这种情况下,理想情况下,您可能需要一种不同于可调大小池的机制来调整这一点。例如,您可以启动一个goroutine来检查目标机器的RAM或负载等,如果机器准备就绪,则启动一个任务,如果没有,则安排另一次检查。很明显,我不知道什么是有效的,因为我真的不知道你在建什么。谢谢你!看起来不错。在这种情况下,所有员工都可能在辞职前收到辞职通知。示例: