Golang:匿名结构和空结构

时间:2013-12-27 01:37:24

标签: concurrency go channel goroutine

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将从完成的频道接收值并丢弃接收的值。但为什么我需要这样做?

谢谢!

5 个答案:

答案 0 :(得分:34)

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

The empty struct查看最近的文章“Dave Cheney”(2014年3月)。

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

并保留与结构相关的所有其他优点:

  • 您可以在其上定义方法(该类型可以是方法接收器)
  • 您可以实现一个接口(使用您在空结构上定义的所述方法)
  • as a singleton
  Go中的

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

例如,请参阅定义global var errServerKeyExchange的文件中的empty struct rsaKeyAgreement

答案 1 :(得分:26)

  

Composite literals

     

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

struct{}{}struct{}类型的复合文字,值的类型后跟一个大括号的复合元素列表。

for _ = range langs { <-done }等待所有langs的所有goroutine发送了done条消息。

答案 2 :(得分:8)

  1. struct{}是一种类型(特别是没有成员的结构)。如果您有类型Foo,则可以在包含Foo{field values, ...}的表达式中创建该类型的值。将这些放在一起,struct{}{}struct{}类型的值,这是渠道所期望的。

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

答案 3 :(得分:3)

好问题,

在这种情况下结构通道的重点只是表示完成了有用的事情。通道类型并不重要,他可以使用int或bool来实现相同的效果。重要的是,他的代码以同步的方式执行,他正在进行必要的簿记,以便在关键点发出信号并继续前进。

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

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

type Book struct{

}

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

答案 4 :(得分:1)

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 loop usage

我们使用此行创建6个goroutine以在后台运行:

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

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

如果我们不包括最后一行,我们可能看不到输出(因为主goroutines在任何子goroutines执行fmt.Printf代码之前退出,并且当主goroutine退出时,所有子goroutine将退出它,并且无论如何都没有机会跑。)

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

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