Swift 4 öncesinde, dil, bellek alanlarının örtüşen erişimlerine izin veriyor, belirsiz davranışı önlemek için programcı disiplinine dayanıyordu. Apple, herhangi bir değişkenin birden fazla okuyucu veya bir tek yazar tarafından erişilebileceğini, ancak asla her ikisinin aynı anda olamayacağını zorunlu kılan Exclusivity Yasasını temel bir bellek güvenliği garantisi olarak tanıttı.
Temel sorun, iki değiştirilebilir referansın—veya bir değiştirilebilir ve bir değiştirilemez referansın—aynı bellek konumuna eş zamanlı olarak erişmesidir. Bu senaryo genellikle inout parametreleri, değiştiren yöntemler veya örtüşen kapanış yakalamaları ile ortaya çıkar ve veri yarışlarına, tutarsız anlık görüntülere veya yığın bozulmasına yol açar.
Swift, karma bir uygulama stratejisi uygular. Derleyici, bir işleve iki inout argümanı olarak aynı değişkeni geçmek gibi belirgin ihlalleri derleme zamanında reddetmek için statik def-use analizini gerçekleştirir. Kaçış kapanışları, uzun süreli işlemler veya çalışma zamanına bağımlı takma adlar içeren karmaşık senaryolar için, derleyici dinamik enstrümantasyon ekler. Bu çalışma zamanı izleme, her iş parçacığı için bir erişim seti korur; örtüşen bir değiştirilebilir erişim tespit edildiğinde, program hemen yakalar ve belirsiz davranış sergilemez.
struct SignalProcessor { var waveform: [Float] mutating func amplify(by factor: Float, using buffer: (inout [Float]) -> Void) { buffer(&waveform) } } var processor = SignalProcessor(waveform: [0.1, 0.2, 0.3]) // Çalışma zamanı hatası: 'processor.waveform' üzerine örtüşen erişim processor.amplify(by: 2.0) { wave in processor.waveform = [1.0] // 'wave' inout referansı varken yazma girişimi wave[0] = 0.5 }
Bir gerçek zamanlı ses sentezi uygulaması, UI iş parçacığının dalga formu verilerini görselleştirdiği yüksek öncelikli DispatchQueue üzerinde ses tamponlarını işledi. Hızlı parametre ayarlamaları sırasında aralıklı çökmeler meydana geldi ve çökme günlüklerinde UnsafeMutablePointer işlemlerinde yığın bozulması gösterildi.
Geliştirme ekibi üç ayrı mimari çözüm üzerinde durdu.
os_unfair_lock senkronizasyonu kullanarak uygulama. Paylaşılan AudioBuffer yapısını hafif bir spinlock ile korudular. Bu veri yarışlarını önlese de, ses geri araması (asla bloke olmaması gereken) ile UI iş parçacığı arasında kilit çakışması ses kaybına neden oldu. Ayrıca, UI kilidi tutarken gerçek zamanlı iş parçacığı beklerken öncelik tersine dönmesi meydana geldi ve bu da Core Audio'nun katı zamanlama gereksinimlerini ihlal etti.
Değiştirilemez değer kopyalama kullanarak uygulama. AudioBuffer'ı struct olarak yeniden düzenleyip her çerçevede UI iş parçacığına kopyalarını geçtiler. Bu senkronizasyon ihtiyaçlarını ortadan kaldırdı ama kabul edilemez gecikmelere yol açtı. 60Hz'de 1024 örnekli tamponları kopyalamak, her saniye megabaytlarca geçici bellek kullanarak, Swift'in ARC trafiğini ve Core Foundation tahsisçi baskısını tetikledi ve duyulabilir bozulmalara neden oldu.
Swift'in katı kapsamı ile birlikte Exclusivity kullanarak uygulama. Ses geri aramasının tampona yalnızca iyi tanımlanmış bir kapsam içinde özel erişime sahip olmasını sağlayarak paylaşılan değiştirilebilir durumu ortadan kaldırdılar ve işleme aşamaları için inout parametrelerini kullandılar. UI, yalnızca okunabilir anlık görüntüler aldı. Bu çözüm, güvenliği kanıtlamak için Swift'in derleme zamanı exclusivity kontrollerini kullanması nedeniyle seçildi; böylece çalışma zamanı senkronizasyonunun aşamasını tamamen ortadan kaldırırken örtüşen değişimi de engelledi.
Yeniden yapılandırma tüm yığın bozulması çökmelerini ortadan kaldırdı. Kilitleme araçlarının ve bellek tahsis değişimlerinin ortadan kaldırılmasıyla CPU kullanımı %40 düştü ve ses boru hattı yoğun yük altında kesintisiz çalışmayı başardı.
Neden exclusivity uygulaması eş zamanlı okuma erişimine izin verirken örtüşen okuma-yazma durumunda tuzak kuruyor ve Swift bunları makine kodu düzeyinde nasıl ayırt ediyor?
Adaylar sıklıkla exclusivity'i genel iş parçacığı güvenliğiyle karıştırır. Swift, durum değiştiremeyecekleri için birden fazla eş zamanlı yalnızca okuma erişimine izin verirken, herhangi bir yazma işlemi exclusivity gerektirir. Makine kodu düzeyinde, derleyici yalnızca okuma erişimi için çalışma zamanı izlemeyi atlar (iş parçacığı denetleyici ile derlendiği durumlar hariç), yazmalar ise çalışma zamanı çağrısı olan swift_beginAccess'i tetikler ve bellek konumunu bir iş parçacığına özgü erişim setinde kaydeder. Çalışma zamanı bir bayrak sistemi (read ile modify arasında) kullanarak çelişmeleri belirler, böylece eş zamanlı okumalar mümkün olur, ancak bir modify bayrağı, her türlü mevcut erişimle karşılaştığında tuzak kurar.
Swift, async/await kodundaki askıya alma noktaları arasında exclusivity ihlallerini nasıl ele alır?
Birçok aday, async/await'in otomatik olarak exclusivity endişelerini çözdüğünü varsayar. Ancak Swift, await'i potansiyel bir erişim sınırı olarak ele alır. Bir görev, bir değişkene inout referansı tutuyorsa ve bir await ile karşılaşırsa, derleyici, erişimin askıya alma öncesinde sona erdiğini kanıtlamak ya da askıya alma boyunca uzatmak zorundadır. Çalışma zamanı, bu erişimleri görev başına izler. Eğer başka bir görev, ilk görev askıya alınırken aynı bellek alanına erişim sağlamaya çalışırsa, çalışma zamanı tuzak kurar. Geliştiriciler, await sınırları boyunca inout referanslarını tutmaktan kaçınmalı ya da durumlarını Actors içinde kapsülleyerek askıya alma boyunca doğru izolasyonu sağlamalıdır.
Hangi belirli derleyici optimizasyon bayrağı altında çalışma zamanı exclusivity kontrolü devre dışı bırakılır ve hangi felaket başarısızlık modları sonuçlanır?
Adaylar sıklıkla exclusivity'nin değişmez olduğunu düşünür. Swift, performans açısından kritik kodlar için tüm çalışma zamanı exclusivity kontrollerini devre dışı bırakan -Ounchecked derleme modunu sağlar. Bu yapılandırmada, örtüşen inout değişiklikleri gibi gecikmiş exclusivity ihlalleri, belirleyici tuzaklar yerine sessiz yığın bozulmasına yol açar. Bu, uzunluk alanlarının artık tampon içerikleriyle eşleşmediği bozulmuş String deposu, sınır dışı bellek erişimine yol açan bozulmuş Array meta verisi veya bozulmuş işaretçiler daha sonra belirlenirse rastgele kod yürütme olarak tezahür edebilir. Bu bayrak, yalnızca resmi doğrulama veya kapsamlı statik analiz ile örtüşen erişimlerin yokluğunun kanıtlandığı durumlarda kullanılmalıdır.