Simpan tugas pengiraan di dalam aktor sebelum penggantungan pertama, kemudian biarkan pemanggil serentak menunggu tugas yang sama. Panduan ini membina contoh setempat jumlah kuasa dua untuk utiliti Mac, dengan peraturan jelas tentang pembatalan, ralat dan pembersihan.
Apakah yang patut dikongsi oleh pemanggil serentak?
Katakan dua bahagian utiliti desktop Mac memerlukan ringkasan pengiraan yang sama: pratonton dan panel butiran. Setiap bahagian meminta jumlah kuasa dua dalam tatasusunan sintetik [1, 2, 3, 4]. Kedua-duanya patut menerima 30, dan permintaan yang menyertai proses aktif yang sama patut berkongsi satu pelaksanaan.
Pelaksanaan di bawah memberikan setiap tika pengira satu tatasusunan input yang tidak berubah. Ia berkongsi tugas semasa selagi tugas itu berada dalam slot proses aktor, kemudian mengosongkan slot apabila pemanggil mengesan penyelesaian. Permintaan seterusnya boleh memulakan pengiraan baharu. Ini ialah perkongsian kerja yang sedang berlangsung; menyimpan jawapan yang telah selesai selama-lamanya memerlukan dasar caching yang berasingan.
Kontrak nilai ini sengaja ringkas:
- Terima paling banyak 100,000 integer, setiap satunya antara
0dengan1,000. - Kembalikan jumlah kuasa duanya; tatasusunan kosong mengembalikan
0. - Sertakan pengecam proses supaya pemanggil dapat membezakan pelaksanaan yang dikongsi daripada jumlah yang kebetulan sama.
- Sebarkan ralat pengiraan kepada pemanggil yang menunggu proses tersebut.
- Benarkan pemanggil individu membatalkan minatnya tanpa membatalkan pekerja yang dikongsi.
Jumlah maksimum yang dibenarkan ialah 100,000 × 1,000² = 100,000,000,000, yang muat dalam Int 64-bit Mac. Had ini sebahagian daripada kontrak contoh ini. Mengubahnya memerlukan pertimbangan semula tentang limpahan aritmetik dan penggunaan sumber.
Mengapakah aktor masih memerlukan tugas yang disimpan?
Aktor melindungi keadaan boleh ubahnya, tetapi kaedah aktor tak segerak boleh digantung. Ketika penggantungan itu, panggilan lain boleh memasuki aktor. Panduan bahasa Swift menerangkan pengasingan aktor dan titik penggantungan, manakala sesi Apple tentang aktor menunjukkan bagaimana andaian yang dibuat sebelum await boleh menjadi lapuk. Concurrency — The Swift Programming Language, Protect mutable state with Swift actors.
Oleh itu, menyemak sama ada jawapan sedia ada tersedia kemudian menunggu pengiraan baharu tidak menghalang kerja berganda. Pemanggil lain boleh tiba sebelum jawapan tersedia. Sebaliknya, terbitkan pemegang tugas secara segerak: periksa slot proses, cipta pekerja hanya jika slot kosong, simpan tugas itu, kemudian tunggu hasilnya.
Apple mendokumenkan bahawa tugas boleh bermula sebaik sahaja dicipta. Mengakses value tak segeraknya menunggu sehingga selesai dan menyebarkan ralat yang dilemparkan. Oleh itu, aktor menyelaraskan pemilikan satu pemegang tugas; pemanggil mendapatkan hasil melalui pemegang tersebut. Task, value.
Bina contoh setempat yang lengkap
Gunakan pengkompil Swift 6 pada macOS 13 atau lebih baharu. Keperluan macOS di sini berpunca daripada panggilan Task.sleep(for:) dalam demonstrasi. Panggilan itu digantung tanpa menyekat benang asas; jeda singkat memudahkan pemerhatian permintaan yang bertindih. Panggilan itu tidak melakukan sebarang pengiraan aritmetik dan boleh dibuang selepas itu. sleep(for:tolerance:clock:).
Simpan program lengkap berikut sebagai SharedCalculation.swift. Program ini menggunakan tetapan lalai biasa yang tidak diasingkan untuk kompilasi baris perintah yang ditunjukkan di bawah. Foundation menyediakan UUID, yang pematuhan yang didokumenkan termasuk Sendable dan Equatable; pengecam ini melabelkan setiap proses dan menyokong pembersihan bersyarat. UUID.
import Foundation
struct CalculationResult: Sendable {
let total: Int
let runID: UUID
}
enum CalculationError: Error {
case invalidInput
}
actor SharedCalculation {
private struct Flight {
let id: UUID
let task: Task<CalculationResult, Error>
}
private let samples: [Int]
private var flight: Flight?
init(samples: [Int]) {
self.samples = samples
}
func value() async throws -> CalculationResult {
try Task.checkCancellation()
let active: Flight
if let existing = flight {
active = existing
} else {
let id = UUID()
let snapshot = samples
let task = Task.detached(priority: .userInitiated) {
[snapshot, id] () async throws -> CalculationResult in
guard snapshot.count <= 100_000,
snapshot.allSatisfy({ (0...1_000).contains($0) })
else {
throw CalculationError.invalidInput
}
// Demonstration only: allow overlap to be observed.
try await Task.sleep(for: .milliseconds(100))
let total = snapshot.reduce(0) { sum, sample in
sum + sample * sample
}
return CalculationResult(total: total, runID: id)
}
active = Flight(id: id, task: task)
flight = active
}
defer {
// An older waiter must not clear a newer flight.
if flight?.id == active.id {
flight = nil
}
}
let result = try await active.task.value
try Task.checkCancellation()
return result
}
}
@main
struct Demo {
static func main() async throws {
let calculation = SharedCalculation(samples: [1, 2, 3, 4])
async let preview = calculation.value()
async let details = calculation.value()
async let inspector = calculation.value()
let (a, b, c) = try await (preview, details, inspector)
precondition(a.total == 30 && b.total == 30 && c.total == 30)
let observedRuns = Set([a.runID, b.runID, c.runID]).count
print("Total: \(a.total)")
print("Runs observed across three requests: \(observedRuns)")
let later = try await calculation.value()
precondition(later.total == 30)
print("Later request started another run: \(later.runID != c.runID)")
}
}
Kompil dan jalankannya dari direktori yang mengandungi fail tersebut:
swiftc -swift-version 6 -parse-as-library SharedCalculation.swift -o SharedCalculation
./SharedCalculation
Penegasan aritmetik sepatutnya lulus. Apabila ketiga-tiga panggilan awal masuk sebelum proses dikosongkan, bilangan proses yang diperhatikan ialah 1. Permintaan kemudian tiba selepas panggilan tersebut kembali, jadi ia memulakan pengiraan lain. Jeda itu menggalakkan pertindihan tetapi tidak menjamin penjadualan: permintaan awal yang tertangguh terlalu lama boleh menyebabkan proses baharu bermula kemudian secara sah. Swift tidak tersedia dalam persekitaran penyelidikan, jadi program ini telah diperiksa tetapi tidak dikompil atau dijalankan di sini.
Pekerja menggunakan tugas terpisah kerana pengiraan ini milik pengira kongsi dan bukannya pemanggil tertentu, dan aritmetikanya patut berjalan secara bebas daripada aktor penyelaras. Tugas itu hanya menangkap petikan input dan pengecam. Apple mengesyorkan tugas anak berstruktur apabila tugas tersebut dapat memenuhi jangka hayat yang diperlukan; tugas terpisah memerlukan keputusan eksplisit tentang jangka hayat dan pembatalan. Contoh ini turut menetapkan keutamaannya secara eksplisit. detached(name:priority:operation:).
Bagaimanakah pembersihan mengelakkan pengiraan seterusnya daripada terpadam?
Urutan semak dan simpan tidak mengandungi await, jadi panggilan aktor kedua tidak boleh menyelit antara pemeriksaan slot kosong dengan penerbitan proses baharu. Sebaik sahaja pemanggil pertama digantung pada active.task.value, pemanggil lain boleh masuk, menemui proses tersebut dan menunggu pemegang tugas yang sama.
Pembersihan memerlukan semakan identiti tersendiri kerana beberapa pemanggil boleh menyambung semula daripada tugas siap yang sama pada masa berlainan. Pertimbangkan urutan ini:
| Peristiwa | Slot proses |
|---|---|
| Pemanggil A dan B menyertai proses X | X |
| X selesai; A menyambung semula dan mengosongkan X | Kosong |
| Pemanggil C masuk dan memulakan proses Y | Y |
| B menyambung semula daripada X dan melakukan pembersihan | Y mesti kekal |
Penetapan nil tanpa syarat oleh B akan memadam pemegang Y. Pemanggil keempat kemudian boleh memulakan kerja pendua ketika Y masih berjalan. Membandingkan flight?.id dengan active.id menjadikan pembersihan khusus kepada proses yang benar-benar disertai oleh setiap pemanggil. Urutan ini terhasil daripada pelaksanaan dan tingkah laku kemasukan semula yang didokumenkan. Protect mutable state with Swift actors.
Sempadan perkongsian yang tepat ialah slot proses yang disimpan. Pekerja mungkin sudah selesai sedangkan pemanggil yang menunggunya belum menyambung semula untuk mengosongkan slot. Permintaan yang masuk dalam selang itu masih boleh menerima hasil tugas yang telah selesai tersebut. Selepas slot dikosongkan, permintaan baharu memulakan proses baharu. Selang singkat selepas selesai ini sebahagian daripada kontrak.
Apakah yang berlaku apabila pemanggil membatalkan atau pengiraan gagal?
Pembatalan dalam Swift bersifat koperatif: kod perlu menentukan tempat untuk memeriksanya dan tindakan yang perlu diambil. Contoh ini memeriksa tugas pemanggil sebelum menyertai proses dan sekali lagi selepas berjaya menerima hasil yang dikongsi. Task — Task Cancellation.
Pemanggil yang telah dibatalkan pada pemeriksaan pertama akan melemparkan ralat tanpa memulakan atau menyertai kerja. Pemanggil yang dibatalkan ketika menunggu akan terus menunggu pekerja kongsi; selepas hasil pekerja berjaya diterima, pemeriksaan kedua akan melemparkan CancellationError. Pemanggil lain masih boleh menerima hasil itu kerana pelaksanaan ini tidak pernah meneruskan pembatalan pemanggil individu kepada pekerja.
Dasar ini tidak menyediakan pembatalan menunggu serta-merta. Jika semua pemanggil membatalkan, pekerja tetap selesai. Itu pilihan yang disengajakan untuk pengiraan setempat yang terhad ini. Reka bentuk yang memerlukan pemanggil individu berundur dengan segera memerlukan penjejakan tambahan bagi setiap penunggu dan peraturan yang jelas tentang bila pekerja kongsi patut dihentikan.
Input tidak sah menyebabkan pekerja melemparkan CalculationError.invalidInput. Pemanggil yang sudah berkongsi tugas tersebut menerima ralatnya melalui value, dan pembersihan tertunda mereka melepaskan slot yang sepadan. Permintaan kemudian mencuba semula input tidak berubah yang sama lalu gagal lagi. Tiada jumlah digantikan secara senyap dan tiada unsur tidak sah digugurkan. value.
Terdapat juga perincian eksplisit tentang susunan ralat: jika pemanggil dibatalkan ketika pekerja gagal, ralat pekerja disebarkan sebelum pemeriksaan pembatalan kedua. Jika pekerja berjaya, pemeriksaan pembatalan pemanggil menentukan sama ada pemanggil itu memulangkan hasil.
Bagaimanakah anda patut mengesahkan dan menggunakan corak ini dalam utiliti Mac?
Jumlah yang sama sahaja tidak membuktikan pelaksanaan dikongsi: tiga pengiraan berasingan bagi input ini juga menghasilkan 30. Bandingkan pengecam proses ketika memeriksa pertindihan, dan sahkan hasil angka secara berasingan.
Untuk ujian keserentakan yang deterministik, gantikan jeda demonstrasi dengan pintu tak segerak yang boleh dikawal dan instrumentasikan penyertaan ke dalam proses. Tahan pekerja sehingga semua pemanggil yang dimaksudkan menyertai, kemudian lepaskannya. Semak satu permulaan pekerja, pengecam yang sepadan, jumlah yang betul, penyebaran ralat dan proses baharu selepas pembersihan. Uji juga urutan pembersihan dalam jadual. Menambah tempoh tidur tidak dapat memastikan keadaan tersebut dengan andal.
Dalam aplikasi desktop, kekalkan satu tika pengira untuk pengguna yang menggunakan input yang sama. Mencipta tika baharu bagi setiap panel memberikan setiap panel slot proses yang berasingan. Apabila input sintetik berubah, cipta pengira baharu untuk petikan tersebut; contoh ini tidak mentakrifkan input boleh ubah atau perkongsian berdasarkan kunci.
Gunakan nilai yang dipulangkan pada keadaan UI di aktor utama. Panduan bahasa Swift mendokumenkan @MainActor untuk kerja UI; pengira kongsi boleh terus menyelaraskan keadaannya sendiri secara berasingan. Concurrency — The Swift Programming Language.
Untuk jumlah yang kecil, penciptaan tugas ialah beban tambahan yang tidak perlu; contoh ini menjelaskan peraturan pemilikan supaya boleh digunakan pada pengiraan setempat yang lebih besar. Kekalkan urutan penting ini apabila menyesuaikannya: terbitkan satu tugas sebelum penggantungan, tunggu pemegang tugas itu dan kosongkan hanya proses yang telah selesai.
Sumber
- Concurrency — The Swift Programming Language — Swift.org.
- Task — Dokumentasi Pembangun Apple.
- value — Dokumentasi Pembangun Apple.
- detached(name:priority:operation:) — Dokumentasi Pembangun Apple.
- sleep(for:tolerance:clock:) — Dokumentasi Pembangun Apple.
- UUID — Dokumentasi Pembangun Apple.
- Protect mutable state with Swift actors — Pembangun Apple.