关于并发:匿名结构和空结构

anonymous struct and empty struct

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
", 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 }
}

[第一个问题]

1
 done <- struct{}{}

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

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

这里

1
 make(chan struct{})

完成是struct {}类型的通道

所以我尝试了

1
 done <- struct{}

但这是行不通的。 为什么我需要为这条线加一个括号?

1
 done <- struct{}{}

[第二个问题]

1
 for _ = range langs { <-done }

为什么我需要这条线? 我知道这行是必要的,因为没有这行,就没有输出。 但是,为什么这行呢? 什么使该代码有必要? 我知道<-done是要从已完成的通道接收值并丢弃接收到的值。 但是为什么我需要这样做?


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

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

您可以创建任意数量的struct{}(struct{}{})将它们推送到您的频道:您的内存不会受到影响。
但是您可以将其用于在go例程之间进行信号传输,如"好奇的频道"中所示。

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

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

in Go you can use an empty struct, and store all your data in global variables. There will only be one instance of the type, since all empty structs are interchangeable.

例如,在定义空结构rsaKeyAgreement的文件中查看全局变量errServerKeyExchange


Composite literals

Composite literals construct values for structs, arrays, slices, and
maps and create a new value each time they are evaluated. They consist
of the type of the value followed by a brace-bound list of composite
elements. An element may be a single expression or a key-value pair.

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

for _ = range langs { <-done }正在等待,直到所有langs的所有goroutine发送了done消息为止。


  • struct{}是一种类型(特别是没有成员的结构)。如果您具有类型Foo,则可以使用Foo{field values, ...}在表达式中创建该类型的值。放在一起,struct{}{}是类型struct{}的值,这是通道期望的值。

  • main函数产生warrior goroutine,它们将在完成后写入done通道。最后一个for块从该通道读取数据,确保在所有goroutine完成之前不会返回main。这很重要,因为main完成时程序将退出,而不管是否有其他goroutine正在运行。


  • 好问题,

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

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

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

    1
    2
    3
    type Book struct{

    }

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


    done通道用于接收来自warrior方法的通知,该通知指示工作程序已完成处理。因此通道可以是任何东西,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func warrior(name string, done chan bool) {
        select {
        case opponent := <-battle:
            fmt.Printf("%s beat %s
    ", 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值的通道,并在warrior的末尾发送true。这可行!您也可以将done通道定义为任何其他类型,没关系。

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

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

    1
    2
    3
    4
    type User struct {
        Name string
        Email string
    }

    struct{}没有任何区别,只是它不包含任何字段,而struct{}{}只是其中的一个实例。最好的功能是它不占用存储空间!

    2.用于循环使用

    我们创建6个goroutines并在此行中在后台运行:

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

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

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

    因此,我们等待所有goroutine完成(运行到最后,并向done通道发送消息),然后退出。 done通道在这里是阻塞的通道,这意味着<-done将在此处阻塞,直到从该通道收到消息为止。

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