将同一关键字分发到多个goroutine
我有这样一个模拟(下面的代码),它将相同的关键字分发给多个goroutine,不同的是goroutine使用关键字做事情的时间不同,但是它们可以独立运行,所以它们不需要任何同步。下面给出的解决方案可以清晰地同步goroutine 我只是想抛开这个想法,看看其他人会如何处理这种类型的分发,因为我认为这是相当普遍的,而且其他人以前也考虑过 以下是我想到的一些其他解决方案,以及为什么它们对我来说有点像: 每个关键字一个goroutine 每次新关键字出现时,生成一个goroutine来处理分发 为每个要更新的goroutine提供关键字位掩码或其他内容 这样,一旦所有的工作人员都接触了关键字,它就可以被删除,我们可以继续前进 为每个工人提供自己的工作堆栈 这似乎有点吸引人,只需给每个工作人员一个堆栈来添加每个关键字,但我们最终会遇到一个问题,即占用大量内存,因为它计划运行很长时间 所有这些的问题是,我的代码应该运行很长时间,无人看管,这将导致关键字或goroutine的大量积累,因为懒惰的工作者比其他人花费的时间更长。似乎给每个工作人员提供自己的AmazonSQS队列或者自己实现类似的东西会很好 编辑: 在程序外存储关键字 我只是想用这种方式来代替,我也许可以把关键字存储在程序外,直到他们都抓到它,然后一旦用完就删除它。这对我来说还可以,实际上,我没有占用磁盘空间的问题 无论如何,下面是一个等待一切完成的方法示例:将同一关键字分发到多个goroutine,go,Go,我有这样一个模拟(下面的代码),它将相同的关键字分发给多个goroutine,不同的是goroutine使用关键字做事情的时间不同,但是它们可以独立运行,所以它们不需要任何同步。下面给出的解决方案可以清晰地同步goroutine 我只是想抛开这个想法,看看其他人会如何处理这种类型的分发,因为我认为这是相当普遍的,而且其他人以前也考虑过 以下是我想到的一些其他解决方案,以及为什么它们对我来说有点像: 每个关键字一个goroutine 每次新关键字出现时,生成一个goroutine来处理分发 为每个
package main
import (
"flag"
"fmt"
"math/rand"
"os"
"os/signal"
"strconv"
"time"
)
var (
shutdown chan struct{}
count = flag.Int("count", 5, "number to run")
)
type sleepingWorker struct {
name string
sleep time.Duration
ch chan int
}
func NewQuicky(n string) sleepingWorker {
var rq sleepingWorker
rq.name = n
rq.ch = make(chan int)
rq.sleep = time.Duration(rand.Intn(5)) * time.Second
return rq
}
func (r sleepingWorker) Work() {
for {
fmt.Println(r.name, "is about to sleep, number:", <-r.ch)
time.Sleep(r.sleep)
}
}
func NewLazy() sleepingWorker {
var rq sleepingWorker
rq.name = "Lazy slow worker"
rq.ch = make(chan int)
rq.sleep = 20 * time.Second
return rq
}
func distribute(gen chan int, workers ...sleepingWorker) {
for kw := range gen {
for _, w := range workers {
fmt.Println("sending keyword to:", w.name)
select {
case <-shutdown:
return
case w.ch <- kw:
fmt.Println("keyword sent to:", w.name)
}
}
}
}
func main() {
flag.Parse()
shutdown = make(chan struct{})
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
close(shutdown)
}()
x := make([]sleepingWorker, *count)
for i := 0; i < (*count)-1; i++ {
x[i] = NewQuicky(strconv.Itoa(i))
go x[i].Work()
}
x[(*count)-1] = NewLazy()
go x[(*count)-1].Work()
gen := make(chan int)
go distribute(gen, x...)
go func() {
i := 0
for {
i++
select {
case <-shutdown:
return
case gen <- i:
}
}
}()
<-shutdown
os.Exit(0)
}
主程序包
进口(
“旗帜”
“fmt”
“数学/兰德”
“操作系统”
“操作系统/信号”
“strconv”
“时间”
)
变量(
关闭chan结构{}
count=flag.Int(“count”,5,“要运行的编号”)
)
类型sleepingWorker结构{
名称字符串
睡眠时间,持续时间
陈总国际
}
func NewQuicky(n字符串)sleepingWorker{
var rq睡眠工人
rq.name=n
rq.ch=制造(成交量)
rq.sleep=time.Duration(rand.Intn(5))*time.Second
返回rq
}
func(r睡眠工人)工作(){
为了{
fmt.Println(r.name,“即将入睡,编号:”,假设我正确理解了问题:
恐怕您对此无能为力。您的资源有限(假设所有资源都有限),因此,如果输入的数据写入速度比处理速度快,则需要进行一些同步。最后,整个过程的运行速度将与最慢的工作人员一样快
如果您真的需要尽快从可用的工作人员那里获得数据,您最好添加某种缓冲。但是缓冲区的大小必须受到限制(即使您在云中运行,也会受到您钱包的限制)因此,假设输入的洪流永不停止,它只会将阻塞推迟到将来某个时候,在那里你会再次看到“同步”
你在问题中提出的所有想法都是基于缓冲数据。即使你为每个关键字工作对运行一个例程,这也会在每个例程中缓冲一个元素,除非你实现对例程总数的限制,否则你将耗尽内存。即使你总是为最快的工作人员留出一些空间来生成一个在新的例程中,输入队列将无法传递新项目,因为它将被最慢的工作人员阻塞
如果平均而言,您的输入速度比处理时间慢,但偶尔会出现峰值,缓冲将解决您的问题。如果您的缓冲区足够大,您可以容纳吞吐量的增加,并且您最快的工作人员可能不会注意到任何事情
解决方案?
由于go带有缓冲通道,这是最容易实现的(也是icza在评论中建议的)。只需给每个工人一个缓冲区。如果你知道哪个工人最慢,你可以给它一个更大的缓冲区。在这种情况下,你的机器内存有限
如果您对单机内存限制不满意,那么是的,根据您的想法,您可以“简单地”在硬盘上为每个工作进程存储缓冲区(队列)。但这也是有限的,只是将阻塞场景推迟到以后。这与您的Amazon SQS建议基本相同(您可以将缓冲区保留在云中,但您需要合理限制缓冲区,或者为账单做好准备。)
最后一点需要注意的是,根据您正在构建的系统的不同,在如此大规模的项目中进行缓冲可能不是一个好主意,这样可以为速度较慢的员工建立积压工作——通常不希望在输入流后面有一个员工数小时、数天、数周,而这就是无限缓冲的情况。真正的答案是uld是:提高您最慢的工作人员的处理速度。(并添加一些缓冲以提高体验。)假设我正确理解问题:
恐怕您对此无能为力。您的资源有限(假设所有资源都有限),因此,如果输入的数据写入速度比处理速度快,则需要进行一些同步。最后,整个过程的运行速度将与最慢的工作人员一样快
如果您真的需要尽快从可用的工作人员那里获得数据,您最好添加某种缓冲。但是缓冲区的大小必须受到限制(即使您在云中运行,也会受到您钱包的限制)因此,假设输入的洪流永不停止,它只会将阻塞推迟到将来某个时候,在那里你将开始看到