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

时间:2014-05-23 19:49:06

标签: concurrency go semaphore goroutine worker-process

我试图在Go中实现一个工作池。 go-wiki(以及Channels部分中的Effective Go)提供了边界资源使用的优秀示例。只需创建一个具有与工作池一样大的缓冲区的通道。然后用工作人员填写该频道,并在他们完成后将其发回频道。从通道块接收,直到工作人员可用。所以频道和循环是整个实现 - 非常酷!

或者,可以阻止发送到频道,但同样的想法。

我的问题是在工作池运行时改变它的大小。我不相信有一种方法可以改变频道的大小。我有一些想法,但大多数看起来都太复杂了。 This page实际上使用通道和空结构实现信号量的方式大致相同,但它有同样的问题(这些事情一直在谷歌搜索" golang信号量"。

2 个答案:

答案 0 :(得分:19)

我会反过来这样做。我没有产生许多goroutine(仍然需要相当大的内存)并使用通道来阻止它们,而是将工作者建模为goroutines并使用通道来分配工作。像这样:

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类型可能是个好主意。此外,使用包含type Task string通道的结构代替done,通常用于表示任务已成功执行。

修改:我已经玩了很多,并想出了以下内容:http://play.golang.org/p/VlEirPRk8V。它基本上是相同的例子,具有更好的API。

答案 1 :(得分:2)

一个简单的改变,可以想到有一个控制信号量有多大的通道。 相关部分是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)
}

http://play.golang.org/p/AHOLlAv2LH