去例行公事被阻止

go routine getting blocked

我倾向于去,所以这可能是一个愚蠢的问题。

我似乎无法弄清楚为什么我的一个 go 例程被另一个例程阻止了。我的理解(可能是错误的)go routines 运行 作为独立的轻量级线程所以它们不应该互相阻塞,除非我搞砸了:)

我已经粘贴了下面的代码,非常感谢任何帮助/提示来解决这个问题。

package main
import "fmt"
import "time"
import "sync"



func worker( jobs <-chan int, job2 chan<- int) {
    defer wg.Done()
    for j := range jobs {
        fmt.Println("finished job", j)
        time.Sleep(time.Second/2)

        if(j%3==0){
           job2 <- j   
        }  

   } 
   close(job2)
   fmt.Println("channel job2 closed")
 }

func worker2(job2 <-chan int) {
    defer wg.Done()
    for i:= range job2 {
        fmt.Println(i)
        time.Sleep(time.Second*10)
    } 
}

var wg sync.WaitGroup

func main() {

    wg.Add(2)
    jobs := make(chan int)

    job2 := make(chan int)

    go func() {
        for j := 1; j <= 10; j++ {
             jobs <- j
        }
        close(jobs)
        fmt.Println("channel jobs closed")
    }()

    go worker(jobs,job2)
    go worker2(job2)

    wg.Wait()
    fmt.Println("exiting main")     

}

当我运行这段代码

时,我得到以下输出
finished job 1
finished job 2
finished job 3
finished job 4
3
finished job 5
finished job 6
6
finished job 7
finished job 8
finished job 9
9
finished job 10
channel jobs closed
channel job2 closed
exiting main

然而我期待这样的事情?

finished job 1
finished job 2
finished job 3
finished job 4
3
finished job 5
finished job 6
finished job 7
finished job 8
finished job 9
finished job 10
channel jobs closed
6
9    
channel job2 closed
exiting main

您的例程有点阻塞,因为通道没有缓冲。无缓冲通道上的 write/read 是阻塞操作。因此,根据定义,您的例程必须互相等待。

本质上,你睡半秒是无关紧要的,因为第二个工人睡了 10 秒。这 10 秒将阻止 reads/writes 进入第二个频道。向通道添加缓冲区以解决此问题。

我想指出的其他一些事情是:

  • time.Sleep(time.Second/2) 是行不通的(好吧,它是,但是除以 3 是行不通的)。 time.Sleep 需要一个 time.Duration 参数,它是一个 int64。您需要传递类似 time.Millisecond * 500 的内容,而不是
  • 将通道传递给例程并从未创建通道的例程中关闭它是一种错误的形式。通道的创建和关闭应该包含在一个例程中。如果没有,它可以工作,但维护将成为一场真正的噩梦。
  • 将导入分组,而不是重复 import "package",只需使用 import ( "package1"\n"package2")
  • 如无必要,请不要使用全局变量。在启动所有例程的函数中创建等待组,并将指向它的指针传递给所有例程。包括匿名函数,只是为了安全起见(例如,一旦你开始向通道添加缓冲区)
  • 考虑研究 context.Contextselect 结构。您可以创建一个 context.WithCancel 并在 select 中侦听所有例程中的 ctx.Done()。然后你可以一次性取消所有例程,而无需处理信号并将内容推送到取消通道

演示

我更改了一些内容(主要是频道创建和一些小的代码清理),并创建了一个 playground 示例 here