Published on

Swiftda Actor

Authors

Actor β€” data race himoyasi

Ko'p vazifa bir xil ma'lumotni bir vaqtda o'zgartirganda β€” data race paydo bo'ladi. Bu dasturda kutilmagan xatolar, noto'g'ri natijalar va crash larga olib keladi. Actor bu muammoni hal qiladi β€” u reference type bo'lib (class ga o'xshash), lekin o'z ma'lumotlariga bir vaqtda faqat bitta vazifa kirishi mumkin. Boshqa vazifalar navbatda kutadi.

Actor β€” Swift 5.5 da kiritilgan yangi tur. U class kabi ishlaydi, lekin o'z property va metodlarini izolyatsiya qiladi. Tashqaridan actor ning property yoki metodiga murojaat qilganda await yozish kerak β€” bu navbatda kutish degani.

Data Race muammosi

Quyida ikkita misol bor. Birinchisida oddiy class ishlatiladi β€” 1000 ta parallel vazifa bir xil property ni o'zgartiradi va natija har safar boshqacha bo'ladi (data race). Ikkinchisida actor ishlatiladi β€” natija har doim to'g'ri.

// ═══════════════════════════════════════
//  ❌ DATA RACE β€” class bilan
// ═══════════════════════════════════════
class XavfliHisoblagich {
    var son = 0
}

let hisoblagich = XavfliHisoblagich()

// 1000 ta parallel vazifa β€” bir xil property ni o'zgartiradi
for _ in 0..<1000 {
    Task {
        hisoblagich.son += 1  // ❌ DATA RACE!
        // Ikki task bir vaqtda son = 500 ni o'qiydi
        // Ikkalasi ham son = 501 yozadi
        // Bitta oshirish yo'qoldi!
    }
}

// Natija: 1000 emas, har safar boshqacha son!
// Bu kutilmagan xatolar va crashlarga olib keladi


// ═══════════════════════════════════════
//  βœ… ACTOR BILAN β€” data race yo'q
// ═══════════════════════════════════════
actor XavfsizHisoblagich {
    var son = 0

    // Actor ichida β€” sinxron kirish
    func oshir() {
        son += 1  // βœ… Xavfsiz β€” faqat bitta vazifa kiradi
    }

    func qiymat() -> Int {
        return son
    }
}

let xavfsiz = XavfsizHisoblagich()

for _ in 0..<1000 {
    Task {
        await xavfsiz.oshir()  // await β€” navbat kutadi
    }
}
// Natija: har doim 1000 βœ…

Actor ishlash printsipi

Actor ning ichidagi funksiyalar izolyatsiya ichida ishlaydi. Bir vaqtda faqat bitta vazifa actor ning ichiga kira oladi β€” qolganlari navbatda kutadi. Actor dan tashqarida esa barcha chaqiruvlar await bilan amalga oshiriladi. let bilan e'lon qilingan property larga esa await siz ham kirish mumkin β€” chunki ular o'zgarmaydi va xavfsiz.

actor BankHisobi {
    let egasi: String
    private(set) var balans: Double

    init(egasi: String, balans: Double) {
        self.egasi = egasi
        self.balans = balans
    }

    // Actor ichidagi funksiyalar β€” izolyatsiya ichida
    // Bir vaqtda faqat bitta vazifa chaqira oladi
    func kiritish(summa: Double) {
        balans += summa
        print("\(egasi): +\(summa), balans: \(balans)")
    }

    func yechish(summa: Double) -> Bool {
        guard balans >= summa else {
            print("\(egasi): mablag' yetarli emas")
            return false
        }
        balans -= summa
        print("\(egasi): -\(summa), balans: \(balans)")
        return true
    }
}

// Tashqaridan β€” await bilan
let hisob = BankHisobi(egasi: "Ali", balans: 1000)

Task {
    await hisob.kiritish(summa: 500)       // await kerak!
    let balans = await hisob.balans         // property ham await
    let natija = await hisob.yechish(summa: 200)
}

// Actor diagrammasi:
// Task 1 ─────►│         β”‚
//              β”‚  ACTOR  β”‚ ← faqat bitta kiradi
// Task 2 ─wait─►│ (navbat)β”‚
//              β”‚         β”‚
// Task 3 ─wait─►│         β”‚

nonisolated β€” izolyatsiyadan chiqarish

Ba'zan actor ichidagi metod yoki property ni izolyatsiyadan chiqarish kerak bo'ladi. Masalan, faqat let property larni ishlatadigan metod uchun izolyatsiya ortiqcha. nonisolated kalit so'zi shu maqsadda ishlatiladi β€” bunday metod tashqaridan await siz chaqirilishi mumkin. Lekin nonisolated metod actor ichidagi var property larga kira olmaydi.

actor Foydalanuvchi {
    let id: UUID           // let β€” o'zgarmaydi, izolyatsiya kerak emas
    let ism: String        // let β€” o'zgarmaydi
    var yosh: Int          // var β€” izolyatsiya kerak

    init(id: UUID, ism: String, yosh: Int) {
        self.id = id
        self.ism = ism
        self.yosh = yosh
    }

    // nonisolated β€” bu funksiya izolyatsiya tashqarida
    // Faqat let property larga kirishi mumkin
    // Tashqaridan await siz chaqirish mumkin
    nonisolated func tavsif() -> String {
        return "\(ism) (ID: \(id))"
        // yosh ga kira olmaydi β€” var bo'lgani uchun
    }

    // Oddiy metod β€” izolyatsiya ichida
    func tugKunniNishonla() {
        yosh += 1  // var β€” izolyatsiya kerak
    }
}

let user = Foydalanuvchi(id: UUID(), ism: "Ali", yosh: 25)

// nonisolated β€” await kerak emas
let tavsif = user.tavsif()  // βœ… sinxron

// Oddiy property/metod β€” await kerak
let yosh = await user.yosh  // await kerak
await user.tugKunniNishonla()

Actor va Protocol

Actor ham protocol ga mos kelishi mumkin. Lekin protokol metodlari actor izolyatsiyasiga moslashishi kerak β€” shuning uchun protokoldagi metodlar async deb belgilanishi kerak.

// Actor protokolga mos kelishi mumkin
protocol Saqlash {
    func saqlash(_ matn: String) async
    func oqish() async -> String?
}

actor FaylSaqlash: Saqlash {
    private var mazmun: String?

    func saqlash(_ matn: String) {
        mazmun = matn
    }

    func oqish() -> String? {
        return mazmun
    }
}

🎯 Topshiriq

actor Kesh yarating: private var saqlangan: [String: String]. saqlash(kalit:qiymat:) va olish(kalit:) metodlari bo'lsin. 100 ta parallel Task dan bir xil kalitga yozish/o'qish qiling. Actor tufayli data race bo'lmasligini ko'ring.

Buy mea coffee