Разница между `go print (v)` и `go func () {print (v)} ()`? - PullRequest
0 голосов
/ 31 декабря 2018

Вот код:

type field struct {
    name string
}

func print(p *field) {
    fmt.Println(p.name)
}

func fix1() {
    data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
    for _, v := range data {
        go print(v)
    }
    time.Sleep(time.Millisecond * 200)
}

func wrong1() {
    data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
    for _, v := range data {
        go func() {
            print(v)
        }()
    }
    time.Sleep(time.Millisecond * 200)
}

func main() {
    wrong1()
}

Насколько я понимаю, все функции в функции wrong1 имеют одну и ту же локальную переменную v.В момент выполнения процедуры значение v может быть равно любому значению в data, поэтому функция печатает случайные данные три раза.

Однако я не понимаю, почему функция fix1 ведет себя по-разному (каждое значение в data печатается ровно один раз).

1 Ответ

0 голосов
/ 31 декабря 2018

Неправильно1 (): go func() { print(v) }()


Go: Часто задаваемые вопросы (FAQ)

Что происходитс замыканиями, выполняемыми как goroutines?

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

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

Можно ошибочно ожидать, что a, b, c будет выводом.Вместо этого вы, вероятно, увидите c, c, c.Это связано с тем, что каждая итерация цикла использует один и тот же экземпляр переменной v, поэтому каждое замыкание совместно использует эту единственную переменную.Когда выполняется замыкание, оно печатает значение v во время выполнения fmt.Println, но, возможно, v изменилось с момента запуска программы.

Чтобы связать текущее значение v с каждым замыканием, как онозапускается, необходимо изменить внутренний цикл для создания новой переменной на каждой итерации.Один из способов - передать переменную в качестве аргумента закрытию:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}

В этом примере значение v передается в качестве аргумента анонимной функции.Затем это значение доступно внутри функции как переменная u.

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

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

Ваш wrong1 пример,

for _, v := range data {
    go func() {
        print(v)
    }()
}

Детская площадка: https://play.golang.org/p/0w86nvVMt1g

Выход:

three
three
three

Ваш wrong1 пример, создание новой переменной,

for _, v := range data {
    v := v
    go func() {
        print(v)
    }()
}

Детская площадка: https://play.golang.org/p/z5RCI0ZZU8Z

Вывод:

one
two
three

Ваш wrong1 пример, прохождениепеременная в качестве аргумента,

for _, v := range data {
    go func(v *field) {
        print(v)
    }(v)
}

Детская площадка: https://play.golang.org/p/1JVI7XYSqvv

Выход:

one
two
three

fix1 (): go print(v)


Спецификация языка программирования Go

Вызовы

С учетом выражения f типа функции F,

f(a1, a2, … an)

вызывает f с аргументами a1, a2,… an.За исключением одного особого случая, аргументы должны быть однозначными выражениями, присваиваемыми типам параметров F и оцениваться перед вызовом функции.

Операторы Go

Значение функции и параметры оцениваются как обычно в вызывающей программе.


Ваш пример fix1, оценивающий значение v до вызова функции,

for _, v := range data {
    go print(v)
}

Детская площадка: https://play.golang.org/p/rN3UNaGi-ge

Выход:

one
two
three
...