Ders 21 / 21
Eşzamanlılık Güvenliği
Aktörün tembel özelliğinin eşzamanlı okumalarda bir kez kurulduğunu ve sonucu hiç beklenmeyen async let'in yine de değerlendirildiğini ölçer; Sendable ve @MainActor sınırlarını anlatır, kurs kapanışını yapar.
Yayın Güncelleme
Bu derste
- Aktör yalıtımının ne sağladığını ve await ile aktör sınırının nasıl geçildiğini açıklar
- Aktördeki lazy özelliğin birden çok eşzamanlı görevden okunsa da bir kez değerlendirildiğini ölçer
- Sendable ve @MainActor ile veri yarışının derlemede nasıl engellendiğini ayırt eder
- Sonucu hiç beklenmeyen async let bağlamasının yine değerlendirildiğini gösterir
İçindekiler
Önceki dersler tek akışta çalışan kodun değerlendirme anlarını ölçtü. Birden çok iş aynı anda aynı belleğe dokunduğunda başka bir soru doğar: bir değere aynı anda iki yerden yazılırsa ne olur. Buna veri yarışı (data race) denir ve sonucu tahmin edilemez. Swift 6 bu soruyu çalışma zamanına bırakmaz; katı eşzamanlılık denetimi (strict concurrency checking) yarışa yol açabilecek kodu derlemede reddeder.
Bu ders üç araç anlatır ve iki şey ölçer. Araçlar aktör yalıtımı, Sendable ve @MainActor’dır. Ölçümler bir aktörün tembel özelliğinin ne zaman kurulduğu ve async let ile başlatılan işin ne zaman koştuğudur. Aktörün içindeki sayaçlar düz Int’tir: Probe bir sınıftır, Sendable değildir ve aktör sınırını geçemez. Görevlerin koşma sırası belirsiz olduğundan sonuçlar toplanıp sıralanarak basılır; yalnız sayılar basılır.
// Probe.swift — bir ifadenin ne zaman degerlendirildigini kaydeder
final class Probe {
let label: String
private(set) var evaluations = 0
private(set) var uses = 0
private var evaluatedBeforeUse = false
init(_ label: String) {
self.label = label
}
func eval<T>(_ value: T) -> T {
evaluations += 1
if uses == 0 { evaluatedBeforeUse = true }
return value
}
func use<T>(_ access: () -> T) -> T {
uses += 1
return access()
}
var moment: String {
if evaluations == 0 { return "hic" }
if evaluations > 1 { return "her-kullanimda" }
return evaluatedBeforeUse ? "hemen" : "ilk-kullanimda"
}
func report() {
print("\(label): \(evaluations) degerlendirme, \(uses) kullanim -> \(moment)")
}
}
func tally(_ probes: [Probe]) {
for moment in ["hemen", "ilk-kullanimda", "her-kullanimda", "hic"] {
let count = probes.filter { $0.moment == moment }.count
print("\(moment): \(count)")
}
}
Aktör Yalıtımı
Aktör (actor) durumunu kendi içinde saklayan ve bu duruma aynı anda tek bir görevin dokunmasına izin veren bir tiptir. Sınıfa benzer, ama değişebilir durumuna ve yalıtımlı yöntemlerine dışarıdan await yazmadan erişilemez; değişmez let özellikler ve nonisolated üyeler bunun dışındadır. Dışarıdaki bir görev aktörün yöntemini çağırırken await yazar: çağrı aktörün sırasına girer, aktör o an meşgulse görev bekler. Bu bekleme noktasına askıya alma noktası (suspension point) denir ve aktörün değişebilir durumuna await yazılmadan ulaşılamaz.
Aşağıdaki aktör iki sayaç tutar ve total adlı tembel bir özellik taşır. Özelliğin ifadesi bir yöntemi çağırır; yöntem kaç kez koştuğunu builds içinde sayar.
// Ledger.swift — tembel ozelligi olan aktor
actor Ledger {
private(set) var builds = 0
private(set) var reads = 0
lazy var total: Int = makeTotal()
private func makeTotal() -> Int {
builds += 1
return 42
}
func read() -> Int {
reads += 1
return total
}
}
Ölçüm önce özelliğe hiç dokunmadan sayaçlara bakar, sonra dört görevi aynı anda read çağırmak üzere başlatır.
// main.swift — aktordeki lazy ozellik eszamanli okumalarda bir kez kurulur
let ledger = Ledger()
print("onceki kurulum \(await ledger.builds)")
let values = await withTaskGroup(of: Int.self) { group in
for _ in 1...4 {
group.addTask { await ledger.read() }
}
var all: [Int] = []
for await value in group {
all.append(value)
}
return all
}
print(values.sorted())
print("okuma \(await ledger.reads), kurulum \(await ledger.builds)")
onceki kurulum 0 [42, 42, 42, 42] okuma 4, kurulum 1
Dört görev aynı özelliği okudu ama kurulum bir kez yapıldı. Özelliğe dokunulmadan önce kurulum sıfırdı: tembel ifade tanım satırında değil, ilk okumada çalıştı. Aktör yalıtımı burada iki işi birden yapar. Okumaları sıraya dizer ve ilk okumanın kurulumu bitmeden ikincisinin başlamasına izin vermez. Bu yüzden iki görevin aynı anda tembel ifadeyi çalıştırması ve sayacın bozulması olanaksızdır. Bu dersin anı ilk kullanımdadır.
Bu ölçümün ne söylemediğini de belirtmek gerekir. Aktör, işleri aynı anda yürütmeyi engellemez; yalnız aktörün durumuna dokunan kısmı sıraya dizer. Dört görev aynı anda başlar, ama read yöntemine tek tek girer. Tembel özelliğin tek kez kurulması bu sıralamanın doğrudan sonucudur: ikinci görev girdiğinde özellik zaten kuruludur.
Askıya alma noktasının bir bedeli vardır. Bir aktör yöntemi await ile başka bir işi beklerken aktör boşalır ve sıradaki görev içeri girebilir. Bu yüzden await öncesinde okunan bir değerin sonrasında aynı kaldığı varsayılmaz. Yalıtım iki görevin aynı anda içeride olmasını engeller; beklerken durumun değişmeyeceğini garanti etmez. Bu dersin ölçümlerinde yöntemlerin içinde await bulunmaz, bu yüzden bu durum ölçülmedi.
Aktarılabilir Tip ve Ana Aktör
Bir değer görevler arasında gidip geliyorsa bir yalıtım sınırını geçiyordur. Sınırı geçebilen tiplere Sendable denir; iş adıyla aktarılabilir tip. Sayılar, dizgiler ve yalnız aktarılabilir tipleri saklayan, genel (public) olmayan değer tipleri bu özelliği kendiliğinden taşır; genel tiplerde uyum açıkça yazılır. Aktörler de taşır, çünkü kendi içlerini zaten korurlar. Değiştirilebilir durum tutan sıradan bir sınıf ise taşımaz: iki görev aynı örneğe aynı anda yazabilir.
Probe bu yüzden aktör sınırını geçemez. Aşağıdaki parça yalnız kaynaktır ve çalıştırılmaz: bir Probe, başka bir görevin kapanışına yakalanır. Swift 6 bunu derlemede reddeder; hata metni burada basılmaz.
// Kaynak parca: calistirilmaz
let moved = Probe("tasinan")
let work = Task.detached { moved.eval(1) }
print(await work.value)
Neden reddedildiği bellidir: Task.detached ile başlayan iş ana aktörden bağımsız koşar, moved ise ana aktörün durumudur. Aynı örneğe iki yerden aynı anda dokunulabilirdi ve bu tam olarak veri yarışının tanımıdır. Derleyici örneğin hangi satırda iki yalıtım bölgesine sızdığını gösterir. Çözümlerden biri değeri taşımamak ya da onu yalıtım altına almaktır: sayacı bir aktörün içine koymak, bu dersteki ölçümlerin yaptığı gibi. Derleyici sorunu çalıştırmadan önce yakaladığı için hata, programın bozuk çalıştığı bir gün yerine yazıldığı gün görülür.
Ana aktör (@MainActor) önceden tanımlı, tek bir yalıtım bölgesidir. @MainActor ile işaretlenen bir tipin durumuna yalnız ana aktörde koşan kod dokunabilir; başka bir yerden gelen çağrı await ister. Bir betiğin üst düzey kodu da ana aktörde koşar. Aşağıdaki sınıf bu işareti taşır ve bir sayaç tutar.
// Screen.swift — ana aktore baglanan sinif
@MainActor
final class Screen {
private(set) var taps = 0
func tap() {
taps += 1
}
}
Ölçüm beş görevi arka planda başlatır; her biri tap yöntemini await ile çağırır.
// main.swift — ana aktore bagli sinifa baska gorevlerden await ile erisilir
let screen = Screen()
await withTaskGroup(of: Void.self) { group in
for _ in 1...5 {
group.addTask { await screen.tap() }
}
}
print("dokunus \(screen.taps)")
dokunus 5
Beş çağrı beş artış verdi, kayıp yok. Çağrılar ana aktörün sırasına girdi ve tek tek işlendi. screen bir sınıf olduğu halde görevlere yakalanabildi: ana aktöre bağlı sınıflar aktarılabilir sayılır, çünkü durumlarına yalnız tek bir yalıtım bölgesinden dokunulur.
Görev Grupları
Yukarıdaki ölçümler görev grubu (task group) kullandı. Grup, sayısı çalışma zamanında belirlenebilen alt görevleri başlatır ve hepsinin bitmesini bekler; for await ile sonuçlar bittikçe toplanır. Sonuçların geliş sırası belirsizdir, bu yüzden dersin ölçümleri onları sıralayarak basar. Sayısı derleme anında bilinen birkaç iş için ise async let vardır; her bağlama tek bir alt görevdir; sonucu bağlamanın adıyla await edilerek okunur. Bir async let bağlaması sağ tarafındaki ifadeyi bir alt görev olarak başlatır; sonuç await ile okunur.
Sınırlayıcı Ölçüm
Soru şudur: async let ne zaman başlar. Akla gelen yanıt, sonucun istendiği satırdır, yani await yazılan yer. Bunu sınamak için bir bağlama sonucu okunur, ikincisi hiç okunmaz. Aktör iki bağlamanın da bump yöntemini çağırıp çağırmadığını sayar.
// Counter.swift — cagri sayisini tutan aktor
actor Counter {
private(set) var calls = 0
func bump() -> Int {
calls += 1
return calls
}
}
// main.swift — async let kuruldugu satirda baslar, beklenmese de
let counter = Counter()
do {
async let waited = counter.bump()
async let ignored = counter.bump()
_ = await waited
}
print("cagri \(await counter.calls)")
cagri 2
İki çağrı sayıldı. ignored hiç await edilmedi, ama kapsam bitmeden iş yine koştu: sonucun beklenmemesi işin koşmasına engel olmadı. Kapsam çıkışında Swift beklenmeyen alt görevi iptal eder ve bitmesini bekler; bump iptale bakmadığı için çalışmasını tamamlar. Bu ölçüm bir sınırı gösterir. Sıradan bir çağrıda ifade, çağrının yazıldığı yerde ve çağıran beklenerek değerlendirilir; async let ise beklemeden bir alt görev başlatır. İki kural farklı alanlara aittir: async let bir ifade değil bir görevdir, ve yan etkisi sonucun okunup okunmamasından bağımsızdır. Bu yüzden async let için değerlendirme kullanımı beklemez.
BL6. Askıya alma noktası içeren aktör yöntemleri ölçülmedi. Ölçümler tek bir aktör ve dört ya da beş eşzamanlı görevle yapıldı; çok daha kalabalık gruplarda aynı kuralın geçtiği gösterilmedi. Görevlerin hangi sırayla koştuğu ölçülmedi, yalnız sayılar okundu. İptal edilen bir görevin iptale bakan bir yöntemde yarıda kaldığı durum ölçülmedi. Ölçüm yalnız işin beklenmese de koştuğunu gösterir; işin bağlama satırında mı yoksa daha sonra mı başladığı bu ölçümle ayrılamaz.
Özet
- Aktör, durumunu kendi içinde korur ve dışarıdan erişimi
awaitile sıraya dizer; aktörün değişebilir durumunaawaityazmadan erişmek mümkün değildir. - Aktördeki
lazyözellik birden çok eşzamanlı görevden okunsa da bir kez değerlendirilir ve ilk okumada kurulur. Sendableolmayan bir değeri görevler arasında taşımak derlemede reddedilir; çözümlerden biri, bu derste ölçülen, değeri yalıtım altına almaktır.async letbağlaması bir alt görev başlatır; sonucu hiç beklenmese de iş kapsam bitmeden koşar.- Bu dersin anı: ilk kullanımda. Aktörün tembel özelliği eşzamanlı okumalarda yalnız ilk okumada, bir kez kurulur.
Kurs Kapanışı
Bu kursun yirmi dört dersi tek bir soruyu yanıtladı: bir ifade ne zaman değerlendirilir. Her ders bir ifadeyi ölçtü ve anını dört addan biriyle söyledi. Tabloda ders, ölçülen ifade, an ve o dersin sınırı yan yana durur.
| Ders | Ölçülen ifade | An | Sınır |
|---|---|---|---|
| Swift’in Tasarım Hedefleri | let sabitinin sağ tarafı |
hemen |
Aynı ifade tek Probe’a iki kez bağlanırsa kural her-kullanimda der |
| Sabitler ve Değişkenler | Yerel sabitin ilk değeri | hemen |
Başka dosyadaki küresel sabit ilk erişimde kurulur |
| Temel Tipler | let satırındaki tip dönüşümü |
hemen |
Okunmayan demet elemanı da değerlendirilir |
| Dizgi İşlemleri | İfade yerleştirmedeki description çağrısı |
her-kullanimda |
Bir let içinde saklanan dizgi bir kez kurulur |
| İşleçler | Sol taraf doğruyken || işlecinin sağı |
hic |
Sağ tarafı @autoclosure ile almayan fonksiyon onu çağrıdan önce değerlendirir |
| Denetim Akışı | Eşleşmeden sonraki where koşulu |
hic |
Döngüde koşul her turda, while çıkışta bir kez daha sorulur |
| Fonksiyonlar | Varsayılan argüman ifadesi | her-kullanimda |
Verilen argüman gövde onu okumasa da çağrıdan önce değerlendirilir |
| Kapanışlar | Kapanış gövdesi | her-kullanimda |
Yakalama listesindeki ifade kurulurken bir kez değerlendirilir |
| İsteğe Bağlı Değerler | Boş kutuda map dönüştürücüsü |
hic |
Dış katmanı dolu .some(nil) kutusunda dönüştürücü koşar |
| İsteğe Bağlı Açma | if let sağ tarafı |
hemen |
Zincirin boş halkasından sonrası atlanır, aynı satırdaki bağımsız ifade atlanmaz |
| Yapılar ve Sınıflar | Yazarken kopyalanan belgenin kopyası | ilk-kullanimda |
Tek sahipli ya da sınıf alanındaki belgede kopya hiç oluşmaz |
| Numaralandırmalar | İlişkili değerin ifadesi | hemen |
Numaralandırmanın hesaplanan özelliği her okunuşta koşar |
| Özellikler | lazy özelliğin ilk değeri |
ilk-kullanimda |
Yapıda her kopya kendi lazy değerini ayrıca kurar |
| Protokoller | Protokol uzantısındaki varsayılan gerçekleştirim | her-kullanimda |
Anı protokol değil, gereksinimi karşılayan tip belirler |
| Uzantılar | Uzantıyla eklenen hesaplanan özellik ve yöntem | her-kullanimda |
Uzantıdaki static let ilk erişimde bir kez kurulur |
| Genellikler | lazy.map dönüştürücüsü |
her-kullanimda |
Tembel dizinin okunmayan elemanı hiç dönüştürülmez |
| Hata Yönetimi | throw sonrasındaki ifade |
hic |
defer bloğu her çıkışta koşar |
| Erişim Denetimi | private lazy önbellek |
ilk-kullanimda |
Anı erişim düzeyi değil, özelliğin çeşidi belirler |
| Otomatik Referans Sayımı | deinit gövdesi |
her-kullanimda |
İkinci referans varken bırakma deinit’i koşturmaz |
| Güçlü, Zayıf ve Sahipsiz Referanslar | Tutma döngüsündeki deinit gövdesi |
hic |
Gitmiş örneğe unowned okuma ölçülemez, programı durdurur |
| Kapanışlarda Yakalama | Yakalama listesindeki ifade | hemen |
Saklanmayan kapanış döngü kurmaz |
| Özellik Sarmalayıcıları | wrappedValue okuyucusu |
her-kullanimda |
@autoclosure alan sarmalayıcı anı ilk-kullanimda’ya çevirir |
| Sonuç Kurucular | Seçilmeyen dalın ifadesi | hic |
Hangi dalın seçileceği koşumda belirlenir |
| Eşzamanlılık Güvenliği | Aktördeki lazy özellik |
ilk-kullanimda |
Sonucu beklenmeyen async let yine koşar |
Yirmi dört dersin dağılımı şöyledir: altısı hemen, dördü ilk-kullanimda, sekizi her-kullanimda, altısı hic. Bu sayım tablonun an sütunundan çıkar. Kursun çekirdeğindeki sekiz ifadenin dağılımı ise 2, 1, 3 ve 2’dir. İkisi aynı şey değildir: çekirdek sekiz ifadeyi, tablo yirmi dört dersi sayar. Bir dersin anı o dersin ölçtüğü ifadeye aittir.
Kursun bitirdiği iş, ifadeyi kuralıyla birlikte okumayı öğretmektir. Aynı ifade yazılışına göre dört andan her birini verebilir: yerel sabit hemen, hesaplanan özellik her-kullanimda, lazy özellik ilk-kullanimda, seçilmeyen dal hic. Kursun bıraktığı alışkanlık, bir ifadeye bakınca onun yazılışından anını tahmin etmeye çalışmak yerine hangi kuralın altında durduğunu sormaktır. Bu kurs dilin kendisini ölçtü.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.