如何处理可以无阻塞增长的队列
How to process a queue that can grow without blocking
如果队列可以从处理函数本身增长,我正在尝试了解如何在 Go 中处理队列。请参见下面的代码。
在此伪代码中,我想将我创建的处理程序数量限制为 10。因此我创建了 10 个处理队列的处理程序。然后我用 url 开始排队。
我的问题是,根据文档,sender
到通道将阻塞,直到接收方接收到数据。在下面的代码中,每个进程都是一个处理新 url 的接收器。然而,很容易看出,如果一个进程向队列发送 11 links,它将阻塞直到所有接收者完成处理这些新的 links。如果这些接收者每个都有 1 link,那么它们也会在将新的 1 link 发送到队列时阻塞。由于每个人都被阻止,所以什么都没有完成。
我想知道 go 的一般解决方案是什么,用于处理可以从进程本身增长的队列。请注意,我认为我可以通过锁定名为 queue
的数组来执行此操作,但我正在尝试了解如何使用通道来完成此操作。
var queue = make(chan string)
func process(){
for currentURL := range queue {
links, _ := ... // some http call that gets links from a url
for _, link := links {
queue <- link
}
}
}
func main () {
for i :=0; i < 10; i++ {
go process()
}
queue <- "https://whosebug.com"
...
// block until receive some quit message
<-quit
}
您可以做两件事,要么使用缓冲通道不阻塞,即使另一端没有人可以接收。这样您就可以立即刷新通道内的值。
一种更有效的方法是检查通道中是否有可用的值,或者通道是否关闭,当所有值都发送完后,发送者应该关闭该通道。
Receivers can test whether a channel has been closed by assigning a
second parameter to the receive expression.
v, ok := <-ch
ok
是 false
如果没有更多的值可以接收并且通道已关闭。使用 select as
检查通道内的值
package main
import (
"fmt"
"sync"
)
var queue = make(chan int)
var wg sync.WaitGroup
func process(){
values := []int{1,2,5,3,9,7}
for _, value := range values {
queue <- value
}
}
func main () {
for i :=0; i < 10; i++ {
go process()
}
wg.Add(1)
go func(){
defer wg.Done()
for j:=0;j<30;j++ {
select {
case <-queue:
fmt.Println(<-queue)
}
}
}()
wg.Wait()
close(queue)
}
您可以使用的一个简单方法是将添加频道链接的代码移到它自己的 go 例程中。
这样,您的主要处理可以继续,而阻塞的通道写入将阻塞一个单独的 go 例程。
func process(){
for currentURL := range queue {
links, _ := ... // some http call that gets links from a url
for _, link := links {
l := link // this is important! ...
// the loop will re-set the value of link before the go routine is started
go func(l) {
queue <- link // we'll be blocked here...
// but the "parent" routine can still iterate through the channel
// which in turn un-blocks the write
}(l)
}
}
}
使用信号量示例编辑以限制 go 例程:
func main () {
maxWorkers := 5000
sem := semaphore.NewWeighted(int64(maxWorkers))
ctx := context.TODO()
for i :=0; i < 10; i++ {
go process(ctx)
}
queue <- "https://whosebug.com"
// block until receive some quit message
<-quit
}
func process(ctx context.Context){
for currentURL := range queue {
links, _ := ... // some http call that gets links from a url
for _, link := links {
l := link // this is important! ...
// the loop will re-set the value of link before the go routine is started
// acquire a go routine...
// if we are at the routine limit, this line will block until one becomes available
sem.Acquire(ctx, 1)
go func(l) {
defer sem.Release(1)
queue <- link // we'll be blocked here...
// but the "parent" routine can still iterate through the channel
// which in turn un-blocks the write
}(l)
}
}
}
尽管此选项最终可能会导致死锁...假设所有 go 例程都已声明,父循环可能会锁定在 sem.Acquire
。这将导致子例程永远不会添加到通道中,因此永远不会执行延迟的 sem.Release
。在我的脑海中,我正在努力想出一个很好的方法来处理这个问题。也许是外部内存队列而不是通道?
如果队列可以从处理函数本身增长,我正在尝试了解如何在 Go 中处理队列。请参见下面的代码。
在此伪代码中,我想将我创建的处理程序数量限制为 10。因此我创建了 10 个处理队列的处理程序。然后我用 url 开始排队。
我的问题是,根据文档,sender
到通道将阻塞,直到接收方接收到数据。在下面的代码中,每个进程都是一个处理新 url 的接收器。然而,很容易看出,如果一个进程向队列发送 11 links,它将阻塞直到所有接收者完成处理这些新的 links。如果这些接收者每个都有 1 link,那么它们也会在将新的 1 link 发送到队列时阻塞。由于每个人都被阻止,所以什么都没有完成。
我想知道 go 的一般解决方案是什么,用于处理可以从进程本身增长的队列。请注意,我认为我可以通过锁定名为 queue
的数组来执行此操作,但我正在尝试了解如何使用通道来完成此操作。
var queue = make(chan string)
func process(){
for currentURL := range queue {
links, _ := ... // some http call that gets links from a url
for _, link := links {
queue <- link
}
}
}
func main () {
for i :=0; i < 10; i++ {
go process()
}
queue <- "https://whosebug.com"
...
// block until receive some quit message
<-quit
}
您可以做两件事,要么使用缓冲通道不阻塞,即使另一端没有人可以接收。这样您就可以立即刷新通道内的值。
一种更有效的方法是检查通道中是否有可用的值,或者通道是否关闭,当所有值都发送完后,发送者应该关闭该通道。
Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression.
v, ok := <-ch
ok
是 false
如果没有更多的值可以接收并且通道已关闭。使用 select as
package main
import (
"fmt"
"sync"
)
var queue = make(chan int)
var wg sync.WaitGroup
func process(){
values := []int{1,2,5,3,9,7}
for _, value := range values {
queue <- value
}
}
func main () {
for i :=0; i < 10; i++ {
go process()
}
wg.Add(1)
go func(){
defer wg.Done()
for j:=0;j<30;j++ {
select {
case <-queue:
fmt.Println(<-queue)
}
}
}()
wg.Wait()
close(queue)
}
您可以使用的一个简单方法是将添加频道链接的代码移到它自己的 go 例程中。 这样,您的主要处理可以继续,而阻塞的通道写入将阻塞一个单独的 go 例程。
func process(){
for currentURL := range queue {
links, _ := ... // some http call that gets links from a url
for _, link := links {
l := link // this is important! ...
// the loop will re-set the value of link before the go routine is started
go func(l) {
queue <- link // we'll be blocked here...
// but the "parent" routine can still iterate through the channel
// which in turn un-blocks the write
}(l)
}
}
}
使用信号量示例编辑以限制 go 例程:
func main () {
maxWorkers := 5000
sem := semaphore.NewWeighted(int64(maxWorkers))
ctx := context.TODO()
for i :=0; i < 10; i++ {
go process(ctx)
}
queue <- "https://whosebug.com"
// block until receive some quit message
<-quit
}
func process(ctx context.Context){
for currentURL := range queue {
links, _ := ... // some http call that gets links from a url
for _, link := links {
l := link // this is important! ...
// the loop will re-set the value of link before the go routine is started
// acquire a go routine...
// if we are at the routine limit, this line will block until one becomes available
sem.Acquire(ctx, 1)
go func(l) {
defer sem.Release(1)
queue <- link // we'll be blocked here...
// but the "parent" routine can still iterate through the channel
// which in turn un-blocks the write
}(l)
}
}
}
尽管此选项最终可能会导致死锁...假设所有 go 例程都已声明,父循环可能会锁定在 sem.Acquire
。这将导致子例程永远不会添加到通道中,因此永远不会执行延迟的 sem.Release
。在我的脑海中,我正在努力想出一个很好的方法来处理这个问题。也许是外部内存队列而不是通道?