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或负载等,如果机器准备就绪,则启动一个任务,如果没有,则安排另一次检查。很明显,我不知道什么是有效的,因为我真的不知道你在建什么。谢谢你!看起来不错。在这种情况下,所有员工都可能在辞职前收到辞职通知。示例: