анонимная структура и пустая структура

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 }
}

[1-й вопрос]

 done <- struct{}{}

Как и зачем нам нужна эта странная структура? Это пустая структура или анонимная структура? Я погуглил, но не смог найти правильный ответ или документацию, объясняющую это.

Первоисточник взят из выступления Эндрю Джерранда http://nf.wh3rd.net/10things/#10

Здесь

 make(chan struct{})

done — это канал типа struct{}

Поэтому я попытался с

 done <- struct{}

Но это не работает. Зачем мне нужны дополнительные скобки для этой строки?

 done <- struct{}{}

[2-й вопрос]

 for _ = range langs { <-done }

Зачем мне эта линия? Я знаю, что эта строка необходима, потому что без этой строки нет вывода. Но почему и что делает эта строка? И что делает это необходимым в этом коде? Я знаю, что <-done должен получать значения из сделанного канала и отбрасывать полученные значения. Но зачем мне это делать?


person Community    schedule 27.12.2013    source источник


Ответы (5)


Составные литералы

Составные литералы создают значения для структур, массивов, срезов и карт и создают новое значение каждый раз, когда они вычисляются. Они состоят из типа значения, за которым следует заключенный в фигурные скобки список составных элементов. Элемент может быть одним выражением или парой ключ-значение.

struct{}{} — это составной литерал типа struct{}, типа значения, за которым следует заключенный в фигурные скобки список составных элементов.

for _ = range langs { <-done } ждет, пока все горутины для всех langs отправят done сообщений.

person peterSO    schedule 27.12.2013

Обратите внимание, что один интересный аспект использования struct{} для типа, передаваемого в канал (в отличие от int или bool), заключается в том, что size пустой структуры равен... 0!

См. недавнюю статью Пустая структура (март 2014 г.) автора Дэйв Чейни.

Вы можете создать столько struct{}, сколько хотите (struct{}{}), чтобы отправить их на свой канал: это не повлияет на вашу память.
Но вы можете использовать его для передачи сигналов между подпрограммами go, как проиллюстрировано в любопытных каналах.

finish := make(chan struct{})

Поскольку поведение close(finish) зависит от сигнализации о закрытии канала, а не от отправленного или полученного значения, объявление finish значением type chan struct{} говорит о том, что канал не содержит значения; нас интересует только его закрытое свойство.

И вы сохраняете все другие преимущества, связанные со структурой:

  • вы можете определить на нем методы (этот тип может быть приемником метода)
  • вы можете реализовать интерфейс (с указанными методами, которые вы просто определяете в своей пустой структуре)
  • как одиночка

в Go вы можете использовать пустую структуру и хранить все свои данные в глобальных переменных. Будет только один экземпляр типа, так как все пустые структуры взаимозаменяемы.

См., например, глобальную переменную errServerKeyExchange в файле, где < определяется href="http://golang.org/src/pkg/crypto/tls/key_agreement.go#L24" rel="noreferrer">пустая структура rsaKeyAgreement.

person VonC    schedule 25.03.2014
comment
Я думаю, что это самая важная причина использовать struct{} вместо всего остального, когда вы просто хотите сделать сигнализацию. - person K1ngjulien; 24.02.2020
comment
@K1ngjulien_ Согласен. Я уделил немного больше внимания сигнализации в (теперь отредактированном) ответе. - person VonC; 24.02.2020

  1. struct{} — это тип (в частности, структура без элементов). Если у вас есть тип Foo, вы можете создать значение этого типа в выражении с Foo{field values, ...}. Собрав это вместе, struct{}{} является значением типа struct{}, что и ожидает канал.

  2. Функция main порождает горутины warrior, которые после завершения записывают в канал done. Последний блок for считывается из этого канала, гарантируя, что main не вернется, пока не закончатся все горутины. Это важно, потому что программа завершится, когда main завершится, независимо от того, запущены ли другие горутины.

person James Henstridge    schedule 27.12.2013

Хорошие вопросы,

Весь смысл канала структуры в этом сценарии заключается в том, чтобы просто сообщить о завершении, что произошло что-то полезное. Тип канала на самом деле не имеет значения, он мог бы использовать int или bool для достижения того же эффекта. Что важно, так это то, что его код выполняется синхронно, когда он выполняет необходимую бухгалтерию, чтобы сигнализировать и двигаться дальше в ключевых точках.

Я согласен, что синтаксис struct{}{} сначала выглядит странно, потому что в этом примере он объявляет структуру и создает ее в строке, следовательно, второй набор скобок.

Если у вас был уже существующий объект, например:

type Book struct{

}

Вы можете создать его так: b := Book{} вам нужен только один набор скобок, потому что структура Book уже объявлена.

person Ralph Caraveo    schedule 27.12.2013

Канал 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) как канал, который получает логическое значение, и вместо этого отправляем true в конце warrior. Это работает! Вы также можете определить канал done для любого другого типа, это не имеет значения.

<сильный>1. Так что же со странным done <- struct{}{}?

Это просто еще один тип, который будет передан в канал. Это пустая структура, если вы знакомы со следующим:

type User struct {
    Name string
    Email string
}

struct{} не имеет значения, за исключением того, что он не содержит полей, а struct{}{} — это просто его экземпляр. Лучшая особенность в том, что это не требует места в памяти!

<сильный>2. для использования цикла

Мы создаем 6 горутин для запуска в фоновом режиме с помощью этой строки:

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

Мы используем for _ = range langs { <-done }, потому что основная горутина (где выполняется основная функция) не ждет завершения горутины.

Если мы не включим последнюю строку for, скорее всего, мы не увидим никаких выходных данных (поскольку основная горутина завершает работу до того, как какая-либо дочерняя горутина выполнит код fmt.Printf, и когда основная горутина завершается, все дочерние горутины закрываются вместе с ней, и у них не будет возможности все равно беги)

Итак, мы ждем, пока все горутины закончатся (она выполняется до конца и отправляет сообщение на канал done), затем выходим. Канал done здесь является заблокированным каналом, что означает, что <-done здесь будет блокироваться до тех пор, пока не будет получено сообщение из канала.

У нас есть 6 горутин в фоновом режиме, и мы используем цикл for, мы ждем, пока все горутины не отправят сообщение, что означает, что он завершил работу (потому что done <-struct{}{} находится в конце функции).

person cizixs    schedule 27.12.2016