【问题标题】:anonymous struct and empty struct匿名结构和空结构
【发布时间】:2014-01-14 15:00:05
【问题描述】:

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 是一个结构类型的通道{}

所以我尝试了

 done <- struct{}

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

 done <- struct{}{}

[第二个问题]

 for _ = range langs { <-done }

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

【问题讨论】:

    标签: concurrency go channel goroutine


    【解决方案1】:

    Composite literals

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

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

    for _ = range langs { &lt;-done } 正在等待,直到所有 langs 的所有 goroutine 都发送了 done 消息。

    【讨论】:

      【解决方案2】:
      1. struct{} 是一种类型(特别是没有成员的结构)。如果您有一个Foo 类型,您可以使用Foo{field values, ...} 在表达式中创建该类型的值。综上所述,struct{}{}struct{} 类型的值,这是通道所期望的。

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

      【讨论】:

        【解决方案3】:

        好问题,

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

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

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

        type Book struct{
        
        }
        

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

        【讨论】:

          【解决方案4】:

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

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

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

          finish := make(chan struct{})
          

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

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

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

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

          例如在定义了empty struct rsaKeyAgreement 的文件中查看global var errServerKeyExchange

          【讨论】:

          • 我认为这是在您只想做信号时使用 struct{} 而非其他任何东西的最重要原因。
          • @K1ngjulien_ 同意。我在(现已编辑的)答案中更加强调了信号。
          【解决方案5】:

          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) 声明为接收布尔值的通道,并在warrior 的末尾发送true。这行得通!你也可以将done频道定义为任何其他类型,没关系。

          1.那么奇怪的done &lt;- 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 { &lt;-done },因为主 goroutine(main 函数运行的地方)不会等待 goroutine 完成。

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

          所以我们等待所有 goroutine 完成(它运行到最后,并向done 通道发送消息),然后退出。 done channel 这里是阻塞的channel,也就是说&lt;-done会阻塞到这里,直到收到来自channel的消息。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-10-21
            • 2013-04-18
            • 2020-10-28
            • 2018-03-28
            • 2015-02-23
            • 1970-01-01
            • 2016-09-03
            相关资源
            最近更新 更多