programowanieProgramista Backend

Jak w Go zaimplementowane są zamknięcia (closures), jakie pułapki związane są z ich używaniem przy uruchamianiu gorutyn wewnątrz pętli i jak unikać typowych błędów?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

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.

Pytanie z pułapką

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

Przykłady rzeczywistych błędów z powodu nieznajomości subtelności tematu


Historia

W systemie analizy danych produktu dane były anonimizowane w równoległych gorutynach przy pomocy zamknięcia, które zatrzymywało zmienną pętli. W rezultacie, wszystkie równoległe zadania przetwarzały ten sam zestaw danych — wynikiem było zniekształcenie statystyki i niepoprawne raportowanie.

Historia

W chmurowej usłudze integracji backendu Go, zespoły postanowiły zoptymalizować zbieranie metrów, uruchamiając ich przetwarzanie w pętli z wykorzystaniem funkcji anonimowych — wewnątrz gorutyny zatrzymały indeks mapy, co doprowadziło do tego, że część przetworników zbierała dane nie dla swoich usług, lecz dla ostatnio przetworzonego indeksu.

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.