ProgramlamaGo Geliştirici

Go'da for-init-sonlandırıcı döngü nasıl çalışır ve döngü değişkeninin kapsam özellikleri gorutinler ve kapamalar kullanıldığında neden sorunlu hatalara yol açabilir?

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap.

Go'da for döngü yapısı, bir başlatma bloğu (init), koşul kontrolü ve sonlandırıcı ifadenin yanı sıra içerebilir. Tarihsel olarak bu mekanizma, kod yazmayı kolaylaştırmak ve C benzeri dillerin alışkanlığı için oluşturulmuştur. Ancak Go'da döngü değişkeninin (i) kapsamı, yerleşik işlevler, kapamalar ve gorutinler içindeki davranışı güçlü bir şekilde etkileyen bir spesifikaya sahiptir.

Problem — Gorutinler veya kapamalar her döngü iterasyonunda çalıştırıldığında sıkça beklenmedik bir davranış ortaya çıkar: Değişken i kopyalanmaz, bunun yerine "başvuru üzerinden yakalanır", yani kapama döngünün genel değişkenine başvurur, bu da döngü sona erdikten sonra son değeri alır. Bu, tüm gorutinlerde/kapamalarda aynı sonuca yol açar, oysa mantık farklısını öngörebilir.

Çözüm — Her iterasyon değerinin aktarılması gerekiyorsa, değişkenin açık bir şekilde kopyalanmasını (ek bir değişken aracılığıyla) kullanılabilir veya kapamada argüman olarak geçirilebilir.

Kod örneği:

for i := 0; i < 3; i++ { go func(j int) { fmt.Println(j) }(i) // Doğru! Kopyalanmış değer } for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() // Hata: tüm gorutinler 3'ü yazdırır }

Anahtar özellikler:

  • For döngüsünde döngü değişkeni, for bloğunun kapsam alanında örtük olarak ilan edilir
  • Kapamada/gorutinde döngü değişkeninin yakalanması, tüm kapamaların arasındaki değişkenin "paylaşılmasına" yol açacaktır
  • Her iterasyonda değişkenin yeni bir değişkende kopyalanmasıyla aşılır

Hileli sorular.

Break veya continue kullanıldığında döngü değişkeninin kapsamı değişir mi?

Hayır. For'da ilan edilen değişkenin kapsamı daima bu döngü bloğuyla sınırlıdır. Break veya continue yalnızca mevcut iterasyonu keser, ama değişkeni dışarı aktaramaz.

For'un init kısmında ilan edilen bir değişken, döngü dışında bir yöntem içinde yakalanabilir mi?

Hayır. Değişken yalnızca for'un içinde ve ona dahil olan tüm bloklarda görünür, ama döngü tamamlandığında dışarda görünmez.

Eğer değişken yakalama, for'un içinde defer ifadesinde gerçekleşirse ne olur?

Aynı durum: defer fonksiyonu oluşturulduğu zamanda değil, defer çalıştırıldığı zamandaki mevcut değişken değerini "görecektir" (genellikle döngünün son değeri).

for i := 0; i < 3; i++ { defer fmt.Println(i) // tüm defer 3 yazdırır }

Tipik hatalar ve anti-desklenmeler

  • Döngü değişkeninin yeni bir değişkene kopyalanmadan yakalanması
  • Kapama içinde açık bir şekilde geçirilmeksizin döngü değişkeninin anonim bir işlevde geçirilmesi (geç bağlama etkisi)
  • Değişken kapsamını dikkate almadan döngünün içinde defer kullanımı

Gerçek hayattan örnekler

Olumsuz durum

Go web sunucusunda geliştirici, döngü değişkeni olarak bağlantı noktasını kullanarak çeşitli bağlantı noktalarını işlemek için birkaç gorutin başlattı ve bunu doğrudan lambda ifadesinde yakaladı. Tüm gorutinler, dizideki son bağlantı noktasına erişti.

Artılar:

  • Basit, "açık" döngü uygulaması

Eksiler:

  • Hatalı iş mantığı
  • Uzun sürede anlaşılan hata

Olumlu durum

Takım, her zaman döngü değişkeninin değerini yeni bir değişkene kopyalama kuralı getirdi, kapama/gorutin tarafından yakalanacak olan.

Artılar:

  • Beklenmedik yan etkilerin olmaması
  • Kodun şeffaflığı

Eksiler:

  • "Mikrooptimizasyonların" kaybı (yine bir değişken yığında, ama önemsiz)