如何处理可以增长而不会阻塞的队列

时间:2018-08-18 19:08:27

标签: go channel goroutine

我正在尝试了解如何在Go中处理队列,如果该队列可以从处理功能本身扩展而来。参见下面的代码。

在此伪代码中,我想将要处理的处理程序数限制为10。因此,我创建了10个处理队列的处理程序。然后,我使用URL开始队列。

我的问题是,根据文档,通道的sender将阻塞,直到接收者接收到数据为止。在下面的代码中,每个进程都是一个处理新URL的接收器。但是,很容易看到,如果一个进程将11个链接发送到队列,它将阻塞直到所有接收者完成对这些新链接的处理。如果这些接收器每个都具有偶数1个链接,则它们在将新的1个链接发送到队列时也会阻塞。由于每个人都被封锁,所以什么也没有完成。

我想知道处理可从流程本身增长的队列的通用解决方案是什么。请注意,我想可以通过在名为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://stackoverflow.com"
   ...
   // block until receive some quit message
   <-quit 
}

2 个答案:

答案 0 :(得分:1)

您可以使用的一种简单方法是将添加到通道的链接的代码移动到它自己的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)
        }
    }
}

使用信号量示例进行编辑以限制执行例程:

func main () {
    maxWorkers := 5000
    sem := semaphore.NewWeighted(int64(maxWorkers))
    ctx := context.TODO()
    for i :=0; i < 10; i++ {
        go process(ctx)
    }

    queue <- "https://stackoverflow.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。在我头顶上,我正在努力想出一种解决此问题的好方法。也许是外部内存队列而不是通道?

答案 1 :(得分:0)

您可以做两件事,即使用缓冲通道也不阻塞,即使另一端没有人接收。这样,您可以立即刷新通道内的值。

一种更有效的方法是检查通道中是否有可用的值,或者通道是否已关闭,发送所有值时发送方应由该发送者提供。

  

接收者可以通过分配一个频道来测试频道是否已关闭   接收表达式的第二个参数。

v, ok := <-ch 
如果没有更多值要接收并且通道已关闭,则

okfalse。使用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)
}

Playground example

相关问题