GoProgramlamaGo Geliştirici

Go 1.22'deki değişiklikler hangi klasik eski kapanış hatasını çözmüştür?

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

Cevap

Go 1.22'den önce, dil spesifikasyonu döngü değişkenlerini her döngü ifadesi için bir kez değil, her yineleme için bir kez ayırıyordu. Bu tek bellek konumu her yinelemede yeniden kullanıldı ve sadece değeri sıralı olarak değiştirildi. Bir kapanış bu değişkeni referans ile yakaladığında—döngü içinde başlatılan goroutine'lerde yaygın olarak—tüm kapanışlar aynı bellek adresini paylaştı. Sonuç olarak, döngü tamamlandığında her kapanış bu adrese atanan son değeri gözlemledi.

Go 1.22, her yineleme için bir yeni kapsam tanıttı, yani her yineleme farklı bir bellek adresine sahip yeni bir değişken oluşturuyor. Bu, kapanışların o yineleme için belirli bir değeri yakalamasını sağlıyor, paylaşılan değişken bir konumu değil. Bu değişiklik, kodun döngü değişkenlerinin adres kimliğine dayanmadığı durumlarda geriye dönük uyumluluğu korurken, en yaygın eşzamanlılık tuzaklarından birini ortadan kaldırdı.

Hayattan bir durum

Bir veri işleme servisi, sensör okumalarını depolamadan önce paralel doğrulama için işçi goroutine'lere dağıtmak gerekiyordu.

Ekip başlangıçta fan-out işlemini kanıtlayıcı kapanış sözdizimi kullanarak uyguladı:

readings := []SensorReading{{ID: 1}, {ID: 2}, {ID: 3}} for _, r := range readings { go func() { validate(r.ID) // Kritik hata: Tüm goroutine'ler ID 3'ü doğruluyor }() }

Dağıtımın ardından, günlükler her işçinin aynı son kaydı işlediğini, önceki kayıtların tamamen yok sayıldığını ortaya koydu ve veri kaybına yol açtı.

Çözüm 1: Değişken gölgeleme. Bu yaklaşım, döngü gövdesinde döngü değişkenini gölgeleyecek yeni bir değişken tanıtarak her yineleme için farklı bir yığın ayırma zorlar. Artıları: Yakalama sorununu hemen çözer ve işlev imzalarında değişiklik gerektirmez. Eksileri: İnce bir sözdizimsel hileye dayanır; bu, gözden geçirenler için sözdizimsel olarak gereksiz görünebilir ve rastgele kaldırıldığında derleyici koruması sağlamaz.

Çözüm 2: Parametre geçişi. Bu yöntem, değeri kapanışa argüman olarak açık bir şekilde geçer ve değerlendirme her yinelemede gerçekleşir. Artıları: Anlamı nettir, tüm Go sürümleri arasında taşınabilir ve veri bağımlılıklarını açık hale getirir. Eksileri: Kapanışın parametreleri kabul etmesi için yapılandırılmasını gerektirir, bu da minimal ancak sıfırdan fazla sözdizimsel yük ekler.

Çözüm 3: Altyapı güncellemesi. Yeni yineleme değişkeni anlamlarından yararlanmak için tüm filosu Go 1.22'ye veya üstüne geçirme. Artıları: Temel nedenin dil seviyesinde ortadan kaldırılmasını sağlar, böylece daha temiz geleneksel kod yazılmasına olanak tanır. Eksileri: Koordine altyapı değişiklikleri gerektirir ve eski araç zincirlerinde kalması gereken miras kod tabanları için rahatlama sağlamaz.

Ekip, hızlı dağıtım için Çözüm 2'yi seçti. Bu karar, kodun tüm derleyici sürümlerinde doğru çalışmasını sağladı ve dikkatsizce kaldırılabilecek ince gölgeleme hilelerine dayanmadı.

Uygulama sonrası, her goroutine kendi benzersiz sensör kimliğini aldı, boru hattı tüm kayıtları doğru bir şekilde işledi ve sistem, Go 1.22'ye geçiş sırasında stabil kaldı.

Adayların sıklıkla kaçırdığı şeyler

Go 1.22 ve sonrası için bir for-range yineleme değişkeninin adresini almanın neden orijinal dilim öğelerinin doğrudan değiştirilmesine izin vermediğini açıklar mı?

Her yineleme değişkeni, dilim elemanının kopyasını tutar, asıl elemanı değil. Adresini almak, geçici kopyaya bir işaretçi verir, altta yatan dizinin girişine değil. Her yineleme değişkeni farklı bir konumda yer alsa da, değerin kopyasını içerir; *(&v) üzerindeki değişiklik yalnızca geçici kopyayı etkiler, bu da yineleme sona erdiğinde atılır. Kaynak dilimi değiştirmek için dizin sözdizimini kullanmalısınız: for i := range slice { slice[i].Field = NewValue }.

Go 1.22'deki yineleme değişkeni kapsamı değişikliği öncesi 1.22 değişkenlerini yeniden kullanma modeline kıyasla performans yükü veya ek yığın ayırmaları getirir mi?

Hayır. Go derleyicisi, kapanışlar yığına kaçmadığında, yineleme değişkenlerini yığın veya kayıtlar üzerinde optimize eder. Anlamsal değişiklik, sözlük kapsamı ve işaretçi kimliğini etkiler, döngünün tahsis yöntemi veya çalışma zamanı performansı üzerinde değil. Kapanışsız döngüler, değişiklik öncesi ve sonrasında aynı performans özelliklerini sergiler.

Öncelikle 1.22 öncesindeki Go'daki değişkenleri yeniden kullanma davranışı, geleneksel üç maddeli for döngülerini for-range döngülerine kıyasla nasıl etkiledi?

Davranış, tüm for döngü türlerinde aynıydı. Hem for i := 0; i < n; i++ hem de for _, v := range m tüm yinelemelerde döngü değişkenleri için aynı bellek adresini yeniden kullandı. Adaylar, eski kapanış hatasının yalnızca range döngülerine özgü olduğunu varsayıp yanılıyorlar, fakat üç maddeli döngüde indeks i'yi yakalayan kapanışlar da aynı sorundan muzdaripti, beklenen yineleme değeri yerine i'nin son değerini yazdırdı. Go 1.22, tüm döngü türleri için bunu eşit olarak çözmüştür.