2017-08-01 85 views
1

我试图更好地理解golang频道。在阅读this article我与非阻塞玩弄发送和已经拿出下面的代码:为什么在执行第一种情况时,此选择总是运行默认情况?

package main 
import (
    "fmt" 
    "time" 
) 

func main() { 
    stuff := make(chan int) 
    go func(){ 
     for i := 0; i < 5; i ++{ 
      select { 
      case stuff <- i: 
       fmt.Printf("Sent %v\n", i) 
      default: 
       fmt.Printf("Default on %v\n", i) 
      } 
     } 
     println("Closing") 
     close(stuff) 
    }() 
    time.Sleep(time.Second) 
    fmt.Println(<-stuff) 
    fmt.Println(<-stuff) 
    fmt.Println(<-stuff) 
    fmt.Println(<-stuff) 
    fmt.Println(<-stuff) 
} 

这将打印:

Default on 0 
Default on 1 
Default on 2 
Default on 3 
Default on 4 
Closing 
0 
0 
0 
0 
0 

虽然我明白,只有0旨意得到印刷我不明白为什么第一次发送仍然会触发select的default分支?

什么是后面的选择在这种情况下的行为逻辑?

Example at the Go Playground

回答

4

你永远不发送任何值stuff,你执行所有默认的情况下,你去任何在fmt.Println报表接收操作之前。如果没有可以继续的其他操作,则立即采取default的情况,这意味着您的循环将尽快执行并返回。

想要阻止的循环,所以你不需要default情况。你并不需要close末或者,因为你没有依靠封闭的通道疏通接收或从range条款打破。

stuff := make(chan int) 
go func() { 
    for i := 0; i < 5; i++ { 
     select { 
     case stuff <- i: 
      fmt.Printf("Sent %v\n", i) 
     } 
    } 
    println("Closing") 
}() 
time.Sleep(time.Second) 
fmt.Println(<-stuff) 
fmt.Println(<-stuff) 
fmt.Println(<-stuff) 
fmt.Println(<-stuff) 
fmt.Println(<-stuff) 

https://play.golang.org/p/k2rmRDP38f

还要注意的是最后的“已发送”和“关闭”行不打印,因为你没有其他的同步等待够程来完成,但这并不影响这个例子的结果。

+0

所以我的例子'0'打印的是'int'的零值,而不是我认为我会发送的值? – m90

+0

@ m90:是的,未缓冲的频道已经关闭,所以没有收到任何东西。 – JimB

2

只执行默认的情况下,由于对循环运行任何东西开始从通道读取数据前的5倍。每一次,因为没有任何东西可以从频道读取,它会进入默认情况。如果有什么可以从渠道中读取,它会执行这种情况。

1

你的第一种情况下不执行。

这是你的程序在做什么:

  1. 启动够程。
  2. 尝试通过4通道,所有块上发送0,因为没有读取信道,所以通过降低到默认。
  3. 同时,在主要的goroutine,你睡觉一秒...
  4. 然后第二过后,试图从通道读取,但它是封闭的,所以你每次0

为了得到你想要的行为,你有两个选择:

  1. 使用一个缓冲信道,可容纳所有数据的发送:

    stuff := make(chan int, 5) 
    
  2. 不要在您的select语句中使用default,这将导致每个发送等待,直到它可以成功。

哪个是首选取决于您的目标。对于这样一个最低限度的例子,要么可能不会更好或更糟。

+0

我不认为第一次发送实际上是成功的。 https://play.golang.org/p/AN7c7SS98w – Gavin

+0

@Gavin:哦,当然你是对的。 – Flimzy

2

由于您使用的是非阻塞的'发送',stuff <- i只有在读取器已经在等待读取通道中的内容或通道有缓冲区时才会执行。否则,'发送'将不得不阻止。

现在,由于您在从通道读取的打印语句之前有一个time.Sleep(time.Second),直到1秒后才会有通道读取器。另一方面,goroutine在那段时间内完成执行,不发送任何东西。

由于fmt.Println(...)语句是从封闭通道读取的,因此您会看到输出中的全为零。

+1

+1是唯一一个非常明确地指出需要有读者已经在等待切换的人。其他人暗示这一点,但实际上并没有这样解释,所以不能被误解。 – RayfenWindspear

相关问题