29

http://play.golang.org/p/vhaKi5uVmm

package main

import "fmt"

var battle = make(chan string)

func warrior(name string, done chan struct{}) {
    select {
    case opponent := <-battle:
        fmt.Printf("%s beat %s\n", name, opponent)
    case battle <- name:
        // I lost :-(
    }
    done <- struct{}{}
}

func main() {
    done := make(chan struct{})
    langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
    for _, l := range langs { go warrior(l, done) }
    for _ = range langs { <-done }
}

[第一个问题]

 done <- struct{}{}

我们如何以及为什么需要这个看起来很奇怪的结构?它是空结构还是匿名结构?我用谷歌搜索了它,但找不到正确的答案或文档来解释这一点。

原文来自 Andrew Gerrand 的演讲 http://nf.wh3rd.net/10things/#10

这里

 make(chan struct{})

done 是 struct{} 类型的通道

所以我尝试了

 done <- struct{}

但它不起作用。为什么这条线需要一个额外的括号?

 done <- struct{}{}

[第二个问题]

 for _ = range langs { <-done }

为什么我需要这条线?我知道这条线是必要的,因为没有这条线,就没有输出。但是这条线为什么和做什么?是什么使它在这段代码中变得必要?我知道这<-done是从完成的通道接收值并丢弃接收到的值。但为什么我需要这样做?

4

5 回答 5

41

请注意,将 struct{} 用于推送到通道的类型(与 int 或 bool 不同)的一个有趣方面是空结构的大小是...... 0!

请参阅Dave Cheney最近的文章“空结构”(2014 年 3 月)。

您可以根据struct{}需要创建任意数量 ( struct{}{}) 以将它们推送到您的频道:您的记忆不会受到影响。
但是您可以使用它在 go 例程之间发送信号,如“ Curious Channels ”中所示。

finish := make(chan struct{})

由于 的行为close(finish)依赖于通道关闭的信号,而不是发送或接收的值,因此声明finishtype chan struct{}表示通道不包含任何值;我们只对它的封闭属性感兴趣。

并且您保留了与结构相关的所有其他优势:

  • 您可以在其上定义方法(该类型可以是方法接收器)
  • 您可以实现一个接口(使用您刚刚在空结构上定义的方法)
  • 作为单身人士

在 Go 中,您可以使用空结构,并将所有数据存储在全局变量中。该类型只有一个实例,因为所有空结构都是可互换的。

例如,请参见定义空结构的文件中的全局变量。errServerKeyExchangersaKeyAgreement

于 2014-03-25T06:47:23.480 回答
29

复合文字

复合文字为结构、数组、切片和映射构造值,并在每次评估它们时创建一个新值。它们由值的类型和后跟的复合元素的大括号列表组成。元素可以是单个表达式或键值对。

struct{}{}是 type 的复合文字,struct{}值的类型后跟复合元素的大括号绑定列表。

for _ = range langs { <-done }正在等待,直到所有的所有 goroutinelangs都已发送done消息。

于 2013-12-27T01:47:23.343 回答
9
  1. struct{}是一种类型(特别是没有成员的结构)。如果你有一个类型Foo,你可以在一个表达式中创建一个该类型的值Foo{field values, ...}。把它放在一起,struct{}{}就是 type 的一个值struct{},这是通道所期望的。

  2. main函数生成goroutines,当它们完成时warrior将写入通道。done最后一个for块从这个通道读取,确保main在所有 goroutine 完成之前不会返回。这很重要,因为main无论是否有其他 goroutine 在运行,程序都会在完成后退出。

于 2013-12-27T01:47:33.140 回答
4

好问题,

在这种情况下,结构通道的全部意义在于简单地发出信号完成,表明发生了一些有用的事情。通道类型并不重要,他可以使用 int 或 bool 来实现相同的效果。重要的是,他的代码以同步方式执行,他正在做必要的簿记,以发出信号并在关键点继续前进。

我同意struct{}{}起初看起来很奇怪的语法,因为在这个例子中,他声明了一个结构并内联创建它,因此是第二组括号。

如果您有一个预先存在的对象,例如:

type Book struct{

}

你可以像这样创建它: b := Book{},你只需要一组括号,因为 Book 结构已经被声明了。

于 2013-12-27T01:48:15.130 回答
3

done通道用于接收来自warrior方法的通知,表明工作人员已完成处理。所以通道可以是任何东西,例如:

func warrior(name string, done chan bool) {
    select {
    case opponent := <-battle:
        fmt.Printf("%s beat %s\n", name, opponent)
    case battle <- name:
        // I lost :-(
    }
    done <- true
}

func main() {
    done := make(chan bool)
    langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
    for _, l := range langs { go warrior(l, done) }
    for _ = range langs { <-done }
}

我们声明done := make(chan bool)为接收 bool 值的通道,并true在结束时发送warrior。这行得通!您也可以将done通道定义为任何其他类型,没关系。

1.那么奇怪的是done <- struct{}{}什么?

它只是将传递给通道的另一种类型。如果您熟悉以下内容,这是一个空结构:

type User struct {
    Name string
    Email string
}

struct{}没有任何区别,只是它不包含任何字段,并且struct{}{}只是其中的一个实例。最大的特点就是不占用内存空间!

2.for循环使用

我们用这行代码创建了 6 个 goroutine 在后台运行:

    for _, l := range langs { go warrior(l, done) }

我们使用for _ = range langs { <-done }, 因为主 goroutine(main 函数运行的地方)不会等待 goroutine 完成。

如果我们不包括最后一个 for 行,很可能我们看不到任何输出(因为主 goroutine 在任何子 goroutine 执行fmt.Printf代码之前就退出了,而当 main goroutine 退出时,所有子 goroutine 都将随之退出,并且没有任何运行的机会反正)。

所以我们等待所有的 goroutine 完成(它运行到最后,并向done通道发送消息),然后退出。done这里的channel是一个被阻塞的通道,这意味着<-done会阻塞到这里,直到收到来自该通道的消息。

我们在后台有 6 个 goroutines,使用 for 循环,我们等到所有 goroutines 发送一条消息,这意味着它完成了运行(因为done <-struct{}{}is 在函数的末尾)。

于 2016-12-27T09:26:02.647 回答