Sorunun Cevabı
Swift'in eşzamanlılık modeli, 6.0 sürümünde önemli bir güçlendirme geçirdi ve modül sınırlarını kapsayan katı veri izolasyon gereklilikleri getirdi. Katı eşzamanlılık kontrolü ile derlenmiş bir modül, @preconcurrency ile işaretlenmiş eski bir modüle çağrıda bulunduğunda, derleyici güvenliği garanti etmek için yalnızca statik analize güvenemez çünkü çağrılanın uygulaması aktör izolasyon garantilerinden önce olabilir. Bu boşluğu kapatmak için, Swift, işlevin tür bilgileri ve şahit tabloları içinde izolasyon gereksinimlerini meta veri olarak gömülüdür ve çağrı sözleşmesini veya sembol karmaşasını değiştirmeden ABI kararlılığını korur. Çalışma zamanında, üretilen kod, swift_task_isCurrentExecutor içsel işlevini kullanarak mevcut görevlerin gerekli küresel aktör'ün seri yürütücüsünde çalışıp çalışmadığını dinamik olarak kontrol eder; kontrol başarısız olursa, görev ya doğru yürütücüye asenkron olarak eklenir ya da yapılandırmaya bağlı olarak bir tanı crash'i tetiklenir.
Hayattan Bir Durum
Bir finansal teknoloji ekibi, arka plan iş parçacıklarında yoğun istatistik hesaplamaları yapan ve zaman zaman tamamlama işlevleri aracılığıyla UI güncellemeleri gönderen, Swift 5.9 ile yazılmış bir eski analitik SDK (Modül B) sürdürüyordu. Yeni tüketici bankacılığı uygulamalarında (Modül A) Swift 6'yı benimsediklerinde, tüm UI güncellemelerinin MainActor üzerinde gerçekleşmesini sağlamak istediler, ancak hemen SDK'nın tamamını yeniden yazmak istemediler. İzolasyon sınırı sorununu çözmek için üç yaklaşımı değerlendirdiler.
İlk seçenek, SDK'nın tümünde Swift 6 aktörleri ve Sendable türlerini benimsemek için senkron bir yeniden yazım yapmaktı. Bu, derleme zamanı güvenliği ve sıfır çalışma zamanı üstü sağlasa da mühendislik maliyeti yüksek, üç ay olarak tahmin edildi ve kritik hesaplama mantığında yüksek regresyon riski getirdi. İkinci seçenek, Modül A'daki çağrı yerlerinde her SDK geri çağrısını DispatchQueue.main.async içinde manuel olarak sarmalayarak gerçekleştirmekti. Bu yaklaşım açıktı ve SDK değişiklikleri gerektirmiyordu, ancak kolayca gözden kaçabilen, kırılgan ve dağınık ön yüklemeler üretti, bu da yeni geliştiricilerin özellik eklerken potansiyel veri yarışlarına yol açıyordu. Üçüncü seçenek, SDK'nın kamu arayüzünde @preconcurrency anotasyonlarını MainActor izolasyon gereksinimleri ile birleştirmeyi içeriyordu.
Ekip, eski geri çağrıları @preconcurrency @MainActor ile anot ederek üçüncü çözümü seçti. Bu, Modül A'ya bu yöntemleri çağırırken Swift çalışma zamanının geçiş döneminde yürütücü bağlamını dinamik olarak doğrulamasını sağladı. İhlaller meydana geldiğinde—bir arka plan iş parçacığı bir UI geri çağrısını çağırmaya çalıştığında, uygulama hemen hata ayıklama derlemelerinde net tanı ile çöker, bu da geliştiricilerin iptal inançlarını kademeli olarak belirlemelerine ve düzeltmelerine olanak tanır. SDK katı eşzamanlılığa tamamen geçtikten sonra, yalnızca statik izolasyonu zorlamak için @preconcurrency'yi kaldırdılar ve çalışma zamanı izolasyon kontrolü olmayan ve garanti edilmiş bir iş parçacığı güvenliğine sahip bir kod tabanı elde ettiler.
Adayların Sıklıkla Gözden Kaçırdığı
@preconcurrency, bir fonksiyonun ABI'deki karmaşık sembol adını nasıl etkiler ve bu dinamik bağlama için neden önemlidir?
@preconcurrency, bir fonksiyonun karmaşık sembol adını veya düşük seviyeli çağrı sözleşmesini değiştirmez çünkü izolasyon gereksinimleri sembolde değil, tür meta verisinde ve şahit tablolarında kodlanmıştır. Bu tasarım, ABI kararlılığı için kritik öneme sahiptir, çünkü kütüphane yazarlarının mevcut kamu API'lerine aktör izolasyonu eklemesine olanak tanır ve daha önce derlenmiş istemcilerle ikili uyumluluğu bozmaz. Dinamik kontroller, derleyici tarafından çağrı yerinde veya giriş noktasında meta verilere dayalı olarak enjekte edilir, bu da eski ikili dosyaların daha yeni, izolasyon farkında kütüphanelerle sorunsuz bir şekilde bağlanabilmesini sağlar.
Küresel bir aktörün shared örneğinin let olarak tanımlanması ile var olarak tanımlanması arasındaki fark nedir ve bu yürütücünün benzersizliğini nasıl etkiler?
GlobalActor protokolü, temel aktör örneğini döndüren statik bir shared özelliği gerektirir ve bu özellik, tüm işlemler boyunca tekil, süreç-wide benzersiz bir seri yürütücüyü garanti etmek için let sabiti olarak tanımlanmalıdır. Eğer shared bir var olsaydı, yürütücü teorik olarak çalışma zamanında değiştirilebilir, bu da küresel bir aktör'ün tüm izole işlemler için tek bir seri sıra sağladığı temel değişmez ilkesini ihlal ederdi, bu da veri yarışlarına neden olabilir ve izolasyon sınırlarını bozabilirdi. Swift derleyicisi bunu, shared'ın statik değiştirilemez bir özellik olmasını gerektirerek zorlar, bu da swift_task_isCurrentExecutor'ün her zaman tutarlı, tekillik taşıyan bir yürütücü nesnesine karşı karşılaştırma yapmasını garanti eder.
Bir fonksiyon küresel bir aktöre izole edildiğinde, derleyici neden bazen aynı aktörden içeri çağrıldığında bile yürütücüye bir hop yayınlar ve isolated parametre değiştirecisi bunu nasıl optimize eder?
Derleyici, çağrıcının hedef küresel aktör'ün yürütücüsünde zaten çalıştığını statik olarak kanıtlayamadığında, genellikle modül sınırları arasında veya izolasyon bilgileri silinen varlık türleri aracılığıyla çağrıldığında, yürütücüye bir hop ya da en azından bir çalışma zamanı doğrulaması yayınlar. Bu temkinli yaklaşım güvenliği sağlasa da senkronizasyon üstü getirir. Geliştiriciler, çağrıcının izolasyon bağlamını bir argüman olarak açıkça geçiren isolated parametre değiştirecisini (örneğin, func process(isolation: isolated MainActor = #isolation)) kullanarak bunu optimize edebilir; bu, derleyicinin çağrıcının aynı yürütücü üzerinde bulunduğunu kanıtlaması durumunda çalışma zamanı kontrolünü ve hop'u atlatmasına olanak tanır ve çağrıyı bir bağlam değişimi maliyeti olmadan doğrudan bir işlev çağrısına dönüştürür.