Untuk menjalankan perhitungan Swift yang intensif CPU secara paralel, berikan batas @concurrent yang eksplisit pada worker dan bagi pekerjaan independen di antara child task. Contoh Mac ini menjaga pembaruan antarmuka tetap di MainActor dan menggabungkan hasil sintetis tanpa mutasi bersama.
Mengapa menambahkan task masih dapat membuat perhitungan berjalan serial
Misalkan sebuah app Mac memiliki tombol Calculate yang memulai perhitungan lokal yang cukup berat. Membungkus perhitungan di dalam Task { ... } menyediakan titik masuk asinkron, tetapi task yang dibuat dalam konteks main actor dapat mewarisi isolasi tersebut. Loop sinkron yang panjang di dalamnya tetap menyibukkan main actor. Apple menjelaskan perbedaan ini dalam Merangkul konkurensi Swift.
Menambahkan task group juga menyisakan pertanyaan penting: di mana fungsi yang memuat loop mahal itu dijalankan? Jika setiap child memanggil metode perhitungan @MainActor yang sama, actor tersebut menserialkan perhitungan. Actor mengizinkan satu task pada satu waktu untuk mengakses state terisolasinya, seperti dijelaskan dalam bab Concurrency pada panduan bahasa Swift.
Untuk Swift 6.2 dan versi lebih baru, tandai titik masuk perhitungan asinkron dengan @concurrent. Ini secara eksplisit memindahkan eksekusi keluar dari actor pemanggil. Dengan fitur NonisolatedNonsendingByDefault diaktifkan, fungsi asinkron nonisolated biasa justru berjalan pada actor pemanggil; perilaku versi sebelumnya berbeda. Proposal SE-0461 yang telah diterapkan mendokumentasikan batas fitur dan atribut eksplisit tersebut.
Dua pilihan terpisah menghasilkan perilaku yang diinginkan: isolasi worker mengizinkan eksekusi di luar MainActor, dan beberapa child task menyediakan pekerjaan yang dapat dijadwalkan secara independen. Runtime menentukan thread yang tersedia untuk menjalankan task tersebut. Jumlah task maupun @concurrent tidak menjanjikan jumlah core tertentu yang berjalan secara bersamaan. Apple menjelaskan model penjadwalan tersebut dalam Merangkul konkurensi Swift.
Definisikan perhitungan yang dapat dibagi dengan benar
Contoh di bawah memungkinkan pengguna Mac memulai atau membatalkan perhitungan sintetis dan menampilkan checksum akhirnya. Semua input berupa konstanta atau bilangan bulat yang dihasilkan. Perhitungan memiliki kontrak nilai yang tepat sebagai berikut:
- Kunjungi setiap indeks bilangan bulat dalam
0..<counttepat satu kali. - Ubah setiap indeks menjadi
UInt64lalu terapkanx = (x &* 1_664_525) &+ 1_013_904_223berulang kali sebanyak jumlah putaran yang diminta. - Tambahkan nilai yang dihasilkan menggunakan penjumlahan wrapping.
- Kembalikan nol untuk input kosong, tolak count atau rounds negatif, dan tolak batas worker yang tidak positif.
- Lempar error saat pembatalan terdeteksi, alih-alih menampilkan checksum yang belum lengkap sebagai hasil akhir.
Aritmetika wrapping memang disengaja di sini. Aritmetika bilangan bulat biasa di Swift akan memicu trap saat overflow; operator &+ dan &* secara eksplisit mendukung perilaku overflow. Lihat Operator Lanjutan.
Dalam kontrak ini, setiap indeks berdiri sendiri, dan penjumlahan wrapping menggabungkan total parsial modulo 2 pangkat 64. Karena itu, perubahan pengelompokan atau urutan penyelesaian mempertahankan nilai akhir yang persis sama. Kesimpulan ini mengikuti aritmetika yang dipilih. Perhitungan floating-point memerlukan aturan kebenarannya sendiri karena pengelompokan ulang penjumlahan dapat mengubah pembulatan.
Partisi menggunakan pembagian dan sisa: setiap worker menerima jumlah indeks dasar yang sama, dan beberapa worker pertama menerima satu indeks tambahan. Untuk contoh count 17 dan tiga worker, rentangnya adalah 0..<6, 6..<12, dan 12..<17. Rentang tersebut mencakup setiap input tepat satu kali, termasuk sisanya.
Bangun contoh perhitungan Mac yang lengkap
Gunakan toolchain Swift 6.2 atau lebih baru dan target app SwiftUI macOS dengan target deployment macOS 13 atau lebih baru. Ganti entry point app dan content view yang dihasilkan dengan dua blok berikut, yang ditempatkan bersama dalam satu file Swift. Keduanya membentuk satu program lengkap; pertahankan hanya satu deklarasi app @main di target.
Namespace perhitungan secara eksplisit adalah nonisolated, sehingga anggotanya tidak memperoleh isolasi global actor secara inferensi. Swift mendokumentasikan deklarasi tingkat tipe ini dalam SE-0449. Kedua metode perhitungan asinkron menggunakan @concurrent untuk menyatakan batas eksekusinya secara eksplisit.
import Foundation
import SwiftUI
nonisolated enum CalculationError: Error {
case invalidInput
}
nonisolated enum SyntheticCalculator {
@concurrent
static func run(
count: Int,
rounds: Int,
workerLimit: Int
) async throws -> UInt64 {
guard count >= 0, rounds >= 0, workerLimit > 0 else {
throw CalculationError.invalidInput
}
try Task.checkCancellation()
guard count > 0 else { return 0 }
let workers = min(count, workerLimit)
let baseSize = count / workers
let remainder = count % workers
return try await withThrowingTaskGroup(
of: UInt64.self,
returning: UInt64.self
) { group in
var lower = 0
for worker in 0..<workers {
try Task.checkCancellation()
let size = baseSize + (worker < remainder ? 1 : 0)
let start = lower
let end = start + size
lower = end
group.addTask {
try await calculateChunk(
lower: start,
upper: end,
rounds: rounds
)
}
}
var total: UInt64 = 0
for try await partial in group {
total = total &+ partial
}
try Task.checkCancellation()
return total
}
}
@concurrent
private static func calculateChunk(
lower: Int,
upper: Int,
rounds: Int
) async throws -> UInt64 {
try Task.checkCancellation()
var total: UInt64 = 0
for index in lower..<upper {
if index % 256 == 0 {
try Task.checkCancellation()
}
var value = UInt64(index)
for round in 0..<rounds {
if round % 4096 == 0 {
try Task.checkCancellation()
}
value = (value &* 1_664_525) &+ 1_013_904_223
}
total = total &+ value
}
try Task.checkCancellation()
return total
}
}
Setiap child menangkap batas yang immutable dan mengembalikan satu bilangan bulat. Hanya task induk yang memperbarui total; child tidak pernah menambahkan elemen ke koleksi bersama atau mengubah state antarmuka. Task group mendukung eksekusi child secara bersamaan, mensyaratkan hasil yang sendable, dan membatasi masa hidup child dalam cakupan group. Lihat Dokumentasi TaskGroup dari Apple.
Tambahkan kode antarmuka ini ke file yang sama:
@main
@MainActor
struct ParallelCalculationApp: App {
var body: some Scene {
WindowGroup {
CalculationView()
}
}
}
@MainActor
struct CalculationView: View {
@State private var status = "Ready"
@State private var work: Task<Void, Never>?
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Synthetic Calculation")
.font(.title2)
Text(status)
.textSelection(.enabled)
HStack {
Button("Calculate") {
startCalculation()
}
.disabled(work != nil)
Button("Cancel") {
work?.cancel()
}
.disabled(work == nil)
}
}
.padding(24)
.frame(minWidth: 440, minHeight: 180)
.onDisappear {
work?.cancel()
}
}
private func startCalculation() {
guard work == nil else { return }
status = "Calculating…"
let workerLimit = max(
1,
ProcessInfo.processInfo.activeProcessorCount
)
work = Task { @MainActor in
defer { work = nil }
do {
let checksum = try await SyntheticCalculator.run(
count: 1_000_000,
rounds: 128,
workerLimit: workerLimit
)
try Task.checkCancellation()
status = "Checksum: \(checksum)"
} catch is CancellationError {
status = "Cancelled"
} catch {
status = "Calculation failed: \(error)"
}
}
}
}
Antarmuka memiliki handle task luar agar tombol Cancel dapat membatalkannya. Task tersebut tetap terisolasi di main actor, memanggil perhitungan concurrent, lalu memperbarui status yang ditampilkan setelah menunggu hasilnya. Menyimpan handle juga memungkinkan pembatalan saat view menghilang. Dokumentasi Task dari Apple menjelaskan bahwa membuang handle tidak membatalkan task.
Batas worker dimulai dari activeProcessorCount milik Foundation, yang melaporkan core pemrosesan aktif yang tersedia di komputer. Anggap ini sebagai pilihan ukuran awal yang perlu diukur, bukan jumlah task yang dijamin optimal. Makna terdokumentasinya dijelaskan dalam activeProcessorCount.
Periksa kebenaran sebelum membandingkan kinerja
Mulailah dengan input ilustratif yang kecil: ubah pemanggilan menjadi count: 17, rounds: 4, dan workerLimit: 3. Checksum yang diharapkan adalah 18149427060254280572. Ulangi dengan workerLimit: 1; nilainya harus sama. Periksa juga count: 0, yang harus mengembalikan nol, dan count positif yang lebih kecil dari batas worker, yang tetap harus mencakup setiap indeks.
Checksum ilustratif dan cakupan rentang diperiksa dengan reproduksi kecil aritmetika wrapping dalam Python. Compiler Swift tidak tersedia di lingkungan riset, jadi program Swift belum dikompilasi atau dijalankan di sini.
Selanjutnya, pulihkan input yang lebih besar dan bandingkan build yang dioptimalkan menggunakan batas worker satu, dua, dan sejumlah core pemrosesan aktif. Pertahankan count dan rounds, simpan checksum yang dihasilkan, dan ukur beberapa kali. Ini adalah prosedur pengukuran yang disarankan; tidak ada klaim waktu atau percepatan.
Eksekusi dengan satu worker menggunakan batas concurrent dan struktur task group yang sama, sehingga berguna sebagai pembanding untuk implementasi ini. Beban kerja kecil mungkin tidak sepadan dengan overhead partisi dan penjadwalan. Jika indeks dalam perhitungan realistis memiliki biaya yang sangat berbeda, rentang berukuran sama juga dapat membuat satu worker selesai lebih lambat; chunk yang lebih kecil dengan jumlah task aktif yang dibatasi merupakan eksperimen lanjutan yang masuk akal.
Untuk perhitungan yang menghasilkan array, sertakan indeks chunk pada setiap hasil lalu susun chunk sesuai urutan input. Child task group dapat dieksekusi dalam urutan apa pun, dan group menyediakan hasil yang telah selesai saat hasil tersebut tersedia. Checksum menghindari masalah urutan hanya karena reduksi yang dipilih tidak bergantung pada urutan. Lihat TaskGroup.
Tampilkan masalah pembatalan dan isolasi
Pembatalan bersifat kooperatif. Memanggil cancel() menandai task tersebut; kode perhitungan harus memeriksa status itu. Pemeriksaan dalam kedua loop memberi worker kesempatan untuk melempar error, termasuk saat satu indeks memiliki banyak putaran. Interval tersebut merupakan pilihan implementasi, bukan jaminan latensi pembatalan. Apple mendokumentasikan tanggung jawab ini dalam Task.
Loop hasil yang melempar error juga penting. Jika sebuah child melempar error dan error tersebut diteruskan keluar dari body group, child yang tersisa akan dibatalkan. Group tetap menunggu hingga semuanya selesai, jadi pemeriksaan pembatalan di dalamnya tetap diperlukan. Implementasi standard library Swift mendokumentasikan perilaku ini dalam TaskGroup.swift.
Jika antarmuka masih macet, telusuri seluruh jalur pemanggilan yang mahal. Cari worker yang memanggil metode main actor, mengakses properti yang terisolasi actor, atau melakukan persiapan substansial sebelum mencapai batas concurrent. Teruskan nilai bilangan bulat yang diperlukan ke worker dan pertahankan loop panjang di dalam batas tersebut.
Jika compiler menolak @concurrent, periksa versi toolchain. Atribut tersebut diterapkan di Swift 6.2 dan berlaku untuk fungsi asinkron. Untuk toolchain yang lebih lama, model eksekusi asinkron nonisolated sebelumnya didokumentasikan dalam SE-0461; gunakan dokumentasi yang sesuai dengan compiler tersebut, alih-alih menganggap semua versi memberi perilaku identik pada nonisolated async.
Untuk task Mac ini, arsitekturnya sederhana: main actor memiliki state antarmuka, worker concurrent menerima nilai independen, dan task induk menggabungkan hasil lengkap. Pertahankan kontrak nilai saat menyesuaikan ukuran chunk, lalu verifikasi kesamaan checksum dan responsivitas antarmuka sebelum mempertahankan perubahan kinerja.
Sumber
- Merangkul konkurensi Swift — Apple.
- Jalankan fungsi async nonisolated pada actor pemanggil secara default — Swift.org, Swift Evolution.
- Izinkan nonisolated untuk mencegah inferensi global actor — Swift.org, Swift Evolution.
- Concurrency — Swift.org, Bahasa Pemrograman Swift.
- Operator Lanjutan — Swift.org, Bahasa Pemrograman Swift.
- Task — Dokumentasi Pengembang Apple.
- TaskGroup — Dokumentasi Pengembang Apple.
- activeProcessorCount — Dokumentasi Pengembang Apple.
- TaskGroup.swift — Swift.org, standard library Swift.