W Go zamknięcia (closures) to funkcje, które "zamykają" (zatrzymują) zmienne ze swojego otoczenia. Najczęściej zamknięcia są używane dla funkcji anonimowych tworzonych w ramach innych funkcji.
Najczęstszym problemem przy pracy z zamknięciami jest nieoczekiwane zachowanie związane z używaniem zmiennych pętli wewnątrz gorutyny:
for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }
Każda gorutyna może wydrukować tę samą wartość i, ponieważ zmienna i jest cykliczna, a zamknięcie zatrzymuje właśnie zmienną, a nie jej wartość na każdej iteracji.
Prawidłowy sposób:
for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }
Takie zachowanie wynika z tego, że zamknięcie utrzymuje odniesienie do zmiennej ( jej adresu), a nie jej skopiowanej (by value) wartości.
Jaką wartość wydrukują kilka uruchomionych gorutyn wewnątrz pętli, jeśli zatrzymują zmienną pętli?
Odpowiedź: Wszystkie gorutyny mogą wydrukować tę samą wartość (często ostatnią), ponieważ widzą bieżącą wartość zmiennej, a w momencie wykonania gorutyny pętla już zakończyła działanie. Aby tego uniknąć, zmienną należy przekazywać jako parametr do zamknięcia.
Przykład:
for i := 0; i < 5; i++ { go func() { fmt.Println(i) }() } // prawdopodobnie uzyskamy: 5 5 5 5 5
Historia
Historia
Historia
W startupie kurierskim niewłaściwe użycie zamknięć przy aktualizacji współrzędnych zlecenia doprowadziło do tego, że masowo aktualizowane były współrzędne ostatniego zlecenia w tablicy, a nie bieżącego — z powodu wyścigu przy dostępie do tablicy wewnątrz funkcji anonimowej.