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
将从完成的频道接收值并丢弃接收的值。但为什么我需要这样做?
谢谢!
答案 0 :(得分:34)
请注意,对于推送到通道的类型(而不是int或bool),使用struct {}的一个有趣方面是,空结构的大小是...... 0!
请The empty struct查看最近的文章“Dave Cheney”(2014年3月)。
您可以根据需要创建任意数量struct{}
(struct{}{}
)将其推送到您的频道:您的记忆不会受到影响。
但是您可以将它用于go例程之间的信令,如“Curious Channels”中所示。
并保留与结构相关的所有其他优点:
Go中的可以使用空结构,并将所有数据存储在全局变量中。只有一个类型的实例,因为所有空结构都是可互换的。
例如,请参阅定义global var errServerKeyExchange
的文件中的empty struct rsaKeyAgreement
。
答案 1 :(得分:26)
复合文字构造结构,数组,切片和的值 映射并在每次评估时创建新值。他们包括 值的类型后跟一个大括号括号的复合列表 元素。元素可以是单个表达式或键值对。
struct{}{}
是struct{}
类型的复合文字,值的类型后跟一个大括号的复合元素列表。
for _ = range langs { <-done }
等待所有langs
的所有goroutine发送了done
条消息。
答案 2 :(得分:8)
struct{}
是一种类型(特别是没有成员的结构)。如果您有类型Foo
,则可以在包含Foo{field values, ...}
的表达式中创建该类型的值。将这些放在一起,struct{}{}
是struct{}
类型的值,这是渠道所期望的。
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{}{}
位于函数的末尾)。