Показаны сообщения с ярлыком замыкания в Go. Показать все сообщения
Показаны сообщения с ярлыком замыкания в Go. Показать все сообщения

среда, 27 мая 2020 г.

Итерационные переменные и замыкания в Golang

Почему эта программа

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func() {
            fmt.Print(i)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println()
}

печатает

55555

(WaitGroup ожидает завершения набора goroutine.)

Ответ

Существует гонка данных: переменная i совместно используется шестью (6) goroutine.

Гонка данных происходит, когда две goroutine обращаются к одной и той же переменной одновременно, и по крайней мере одно из обращений является записью.

Чтобы избежать этого, используйте локальную переменную и передайте число в качестве параметра при запуске goroutine.

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func(n int) { // Используем локальную переменную.
            fmt.Print(n)
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println()
}

Пример вывода:

40123

Также возможно избежать этой гонки данных, все еще используя замыкание, но тогда мы должны позаботиться об использовании уникальной переменной для каждой goroutine.

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        n := i // Создаем уникальную переменную для каждого замыкания.
        go func() {
            fmt.Print(n)
            wg.Done()
        }()
    }
    wg.Wait()
    fmt.Println()
}


Читайте также:


среда, 13 мая 2020 г.

Анонимные функции и замыкания в Golang

Литерал функции (или лямбда) - это функция без имени.

В этом примере литерал функции передается в качестве аргумента less функции sort.Slice.

func Slice(slice interface{}, less func(i, j int) bool)

people := []string{"Alice", "Bob", "Dave"}
sort.Slice(people, func(i, j int) bool {
    return len(people[i]) < len(people[j])
})
fmt.Println(people)
// Вывод: [Bob Dave Alice]

Вы также можете использовать промежуточную переменную.

people := []string{"Alice", "Bob", "Dave"}
less := func(i, j int) bool {
    return len(people[i]) < len(people[j])
}
sort.Slice(people, less)

Обратите внимание, что функция less является замыканием: она ссылается на переменную people, которая объявлена ​​вне функции.

Замыкания

Литералы функций в Go являются замыканиями: они могут ссылаться на переменные, определенные в окружающей функции. Такие переменные

  • являются общими между окружающей функцией и литералом функции,
  • сохраняются в памяти, пока к ним можно получить доступ.

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

// New возвращает функцию Count.
// Count печатает количество раз, которое она был вызван.
func New() (Count func()) {
    n := 0
    return func() {
        n++
        fmt.Println(n)
    }
}

func main() {
    f1, f2 := New(), New()
    f1() // 1
    f2() // 1 (другая n)
    f1() // 2
    f2() // 2
}


Читайте также:


суббота, 16 февраля 2019 г.

Go FAQ: Что происходит с замыканиями, выполняемыми как go-процедуры?

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

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

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

    // ждем завершения всех go-процедур перед выходом
    for _ = range values {
        <-done
    }
}

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

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

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

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

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

for _, v := range values {
    v := v // создаем новую 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

Это поведение языка, что не происходит определения новой переменной для каждой итерации, возможно, было ошибкой в ​​ретроспективе. Это может быть решено в более поздней версии, но, для совместимости, не может измениться в версии Go 1.


Читайте также: