I denne artikel vil vi udforske de problemer, det har til formål at løse, forklare, hvordan det fungerer under kappen, sammenligne den nye model med den foregående, og tage et nærmere kig på Actor-modellen. Swift Konkurrenceoversigt: Problemer og løsninger At skrive kode, der kører opgaver på samme tid, kan forbedre ydeevnen og responsiviteten, men det introducerer ofte kompleksitet og subtile fejl som raceforhold, deadlocks og tråd-sikkerhedsproblemer. Swift Concurrency, der blev introduceret i Swift 6, har til formål at forenkle samtidig programmering ved at give en klar, sikker og effektiv model til håndtering af asynkrone opgaver. 💡Hvis du bruger Swift 5.x og planlægger at migrere til Swift 6, kan du aktivere Swift Concurrency-checks i dine projektindstillinger. Dette giver dig mulighed for gradvist at vedtage den nye concurrency-model, mens du opretholder kompatibiliteten med eksisterende kode.At aktivere disse checks hjælper med at fange potentielle concurrency-problemer tidligt, hvilket gør overgangen glattere og sikrere.Når du opdaterer din codebase, kan du begynde at integrere async/await-syntaxen og andre Swift Concurrency-funktioner gradvist uden en fuldstændig omskrivning. 💡Hvis du bruger Swift 5.x og planlægger at migrere til Swift 6, kan du aktivere Swift Concurrency-checks i dine projektindstillinger. Dette giver dig mulighed for gradvist at vedtage den nye concurrency-model, samtidig med at kompatibiliteten med eksisterende kode opretholdes. async/await Syntax og andre Swift Concurrency funktioner gradvist uden en fuld omskrivning. Some of the key problems Swift Concurrency addresses include: Race Conditions: Forebyggelse af samtidig adgang til fælles mutable tilstand, der kan forårsage uforudsigelig adfærd. Callback helvede: Forenkling af asynkron kode, der plejede at stole kraftigt på indlejrede callbacks eller færdiggørelse håndtere, hvilket gør koden lettere at læse og vedligeholde. Trådstyringskompleksitet: Abstraktion af lavt niveau skabelse og synkronisering, så udviklere kan fokusere på logik snarere end tråd håndtering. Koordinering af samtidige opgaver: Struktureret samtidighed muliggør klare hierarkier af opgaver med korrekt aflysning og fejlspredning. Ved at udnytte nye sprogfunktioner som Swift 6 giver en mere intuitiv og robust måde at skrive samtidig kode på, hvilket forbedrer både udviklerens produktivitet og app-stabiliteten. async/await Multitasking Moderne operativsystemer og runtimes bruger multitasking til at udføre enheder af arbejde samtidigt. Swift Concurrency vedtager kooperativ multitasking, som adskiller sig fundamentalt fra den forebyggende multitasking model, der anvendes af OS-niveau tråde. Forebyggende multitasking Preventiv multitasking er den model, der anvendes af operativsystemer til at styre tråde og processer. I denne model kan en systemplanlægger tvunget afbryde enhver tråd på næsten ethvert tidspunkt for at udføre en kontekstskifte og tildele CPU-tid til en anden tråd. Dette sikrer retfærdighed i hele systemet og muliggør responsive applikationer - især når man håndterer flere brugerdrevne eller tidssensitive opgaver. Forebyggende multitasking muliggør ægte parallelisme på tværs af flere CPU-kerne og forhindrer misbrug eller langvarige tråde fra at monopolisere systemressourcer. Men denne fleksibilitet kommer til en pris. Fordi tråde kan afbrydes på ethvert tidspunkt i deres udførelse - selv midt i en kritisk operation - skal udviklere bruge synkroniseringsprimitiver som mutexes, semaphores eller atomiske operationer for at beskytte den fælles mutable tilstand. Denne model tilbyder større kontrol og rå concurrence, men det lægger også en betydeligt højere byrde på udviklere.Sikring af trådsikkerhed i et forebyggende miljø er fejl-præget og kan føre til ikke-deterministisk adfærd - adfærd, der varierer fra løb til løb - som er notorisk vanskeligt at begrunde eller pålideligt test. From a technical perspective, preemptive multitasking relies on the operating system to handle thread execution. The OS can interrupt a thread at almost any point — even in the middle of a function — and switch to another. To do this, the system must perform a context switch, which involves saving the entire execution state of the current thread (such as CPU registers, the instruction pointer, and stack pointer), and restoring the previously saved state of another thread. This process may also require flushing CPU caches, invalidating the Translation Lookaside Buffer (TLB), and transitioning between user mode and kernel mode. Disse operationer introducerer betydelig runtime overhead. Hver kontekstswitch tager tid og forbruger systemressourcer - især når kontekstswitches er hyppige eller når mange tråde konkurrerer om begrænsede CPU-kerner.Derudover tvinger forebyggende multitasking udviklere til at skrive trådsikker kode som standard, hvilket øger den samlede kompleksitet og risikoen for samtidige fejl. Mens denne model giver maksimal fleksibilitet og ægte parallelisme, er den ofte overdreven for asynkrone arbejdsprocesser, hvor opgaver typisk bruger det meste af deres tid på at vente på I/O, brugerindtastning eller netværksresponser i stedet for aktivt at bruge CPU'en. Multitasking samarbejde I denne model kører en opgave, indtil den frivilligt giver kontrol – typisk på et ventetidspunkt eller via et eksplicit opkald til I modsætning til traditionelle tråde forhindres samarbejdsopgaver aldrig med tvang. Dette resulterer i forudsigelig udførelse: Kontekstswitches forekommer kun på klart definerede suspensionspunkter. Task.yield() Swift’s cooperative tasks are scheduled onto a lightweight, runtime-managed cooperative thread pool—separate from Grand Central Dispatch queues. Tasks running in this pool are expected to be “good citizens,” yielding control when appropriate, especially during long-running or CPU-intensive work. To support this, Swift provides som et manuelt suspensionspunkt, sikrer, at andre opgaver har en chance for at udføre. Task.yield() However, cooperative multitasking comes with a caveat: if a task never suspends, it can monopolize the thread it’s running on, delaying or starving other tasks in the system. Therefore, it is the developer’s responsibility to ensure that long-running operations include suspension points. I kooperativ multitasking er den grundlæggende enhed for udførelse ikke en tråd, men et stykke arbejde, ofte kaldet en fortsættelse. Funktionen afbrydes ved en , Swift Runtime indfanger den aktuelle eksekveringsstatus i en heap-alloceret fortsættelse. Denne fortsættelse repræsenterer et genoptagelsespunkt og er indstillet til fremtidig eksekvering. async await I stedet for at forbinde en tråd med en langvarig opgave behandler Swift runtime en tråd som en rørledning af fortsættelser. Hver tråd udfører en fortsættelse efter en anden. As mentioned above, this model avoids traditional OS-level context switches. There is no need to save and restore CPU registers or thread stacks; the runtime simply invokes the next closure-like continuation. This makes task switching very fast and lightweight, though it involves increased heap allocations to store the suspended async state. Den vigtigste kompromis: Du bruger lidt mere hukommelse, men får dramatisk lavere overhead til opgavestyring. Introduktion til Task opgave I konkurrenceevne, a en enhed af asynkron arbejde. i modsætning til blot at kalde en Funktion, a er et administreret objekt, der kører samtidig med andre opgaver i et kooperativt thread pool. Task async Task 💡 Opgaverne styres af et kooperativt thread pool. Det kooperative thread pool er designet til at styre concurrency effektivt ved at tillade opgaver at producere CPU'en, mens de venter på at asynkrone operationer skal afsluttes. 💡 Opgaverne styres af et kooperativt thread pool. Det kooperative thread pool er designet til at styre concurrency effektivt ved at tillade opgaver at producere CPU'en, mens de venter på at asynkrone operationer skal afsluttes. Opgaver kan oprettes til at køre samtidig, og de kan også blive ventet eller aflyst. De giver fingrænsekontrol over asynkron adfærd og er en integreret del af struktureret concurrency i Swift. At skabe a Task En opgave kan oprettes ved hjælp af , som straks starter den foreskrevne asynkrone operation: Task Initialiser Initialiser Task(priority: .userInitiated) { await fetchData() } Når du opretter en ved hjælp af den standard initializer (dvs. ikke ), det arver den omkringliggende aktør kontekst, prioritet, og opgave-lokale værdier. Denne adfærd er afgørende for struktureret parallelitet og sikkerhed i parallel kode. Task Detaljeret Detaljeret 💡 Swift 6.2 introducerer en betydelig ændring i, hvordan concurrency håndteres: Som standard kører alle koder på MainActor. For at køre kode på en baggrund tilføjer Swift 6.2 et nyt attribut @concurrent. Du kan også bruge ikke-isoleret, hvis koden ikke kræver adgang til hovedaktøren. 💡 Swift 6.2 introducerer en betydelig ændring i, hvordan concurrency håndteres: Som standard kører alle koder på MainActor. For at køre kode på en baggrund tilføjer Swift 6.2 et nyt attribut @concurrent. Du kan også bruge ikke-isoleret, hvis koden ikke kræver adgang til hovedaktøren. WWDC omfavner hurtig konkurrence WWDC omfavner hurtig konkurrence Under kappen, i tidligere versioner af Swift Concurrency, brugte Swift runtime en intern mekanisme kaldet Selvom denne egenskab ikke var en del af det offentlige API, spillede den en nøglerolle i at sikre, at opgaver, der blev oprettet inden for spillerisoleret kode, ville udføre på den samme aktør, hvilket bevarede datasikkerheden. @_inheritActorContext Med fremskridt i Swift er runtime begyndt at skifte fra Et nyt system kendt som , som nu er mere udtrykkeligt håndteret af compiler og runtime. @_inheritActorContext sending 💡 Hvad er sending? sending er et nyt nøgleord introduceret i Swift 6 som en del af sprogets bevægelse mod sikrere og mere eksplicit samtidighed. Det bruges til at markere funktionsparametre og returnere værdier, der flyttes på tværs af samtidighedens grænser. Det fungerer især godt ikke-kopierbare typer, der sikrer hukommelsessikkerhed og forhindrer fejl efter brug. Når en parameter er markeret med sending, håndhæver compileren, at den oprindelige instans ikke længere er tilgængelig efter overførslen. func process(_ data: sending MyNonCopyableType) async { // `data` is moved here and can’t be used elsewhere after the call } Hvad er sending ? sending er et nyt nøgleord introduceret i Swift 6 som en del af sprogets bevægelse mod sikrere og mere eksplicit parallel. Det bruges til at markere funktionsparametre og returnere værdier, der flyttes over parallelgrænser. Det fungerer især godt ikke-kopierbare typer, der sikrer hukommelsessikkerhed og forhindrer fejl efter brug. sending Compileren håndhæver, at den oprindelige instans ikke længere er tilgængelig efter overførslen. func process(_ data: sending MyNonCopyableType) async { // `data` is moved here and can’t be used elsewhere after the call } When you launch a , skal du sikre, at alle værdier, der er indfanget af opgaven, er Compileren håndhæver nu dette ved brug af compile-time Protokollen og den Funktion af typen. Task.detached Sendable Sendable @Sendable Manglende overensstemmelse kan resultere i en compile-time-fejl, især i strengt concurrency-tilstand. Sendable Opgaver understøtter også prioriteter, svarende til, hvordan Grand Central Dispatch-køer håndterer dem. vs Task Task.detached Når du arbejder med Swift Concurrency, er det vigtigt at forstå forskellen mellem og De definerer, hvordan og hvor asynkron arbejde udføres. Task Task.detached Task Task er arvet den nuværende aktør kontekst (såsom eller enhver brugerdefineret spiller) og prioritering. Det bruges almindeligt, når du vil generere en ny asynkron operation, der stadig respekterer det nuværende strukturerede konvergent træ eller konvergent isolation. Task MainActor Task { await updateUI() } I eksemplet ovenfor, hvis man kalder fra hovedaktøren, Der vil også blive spillet på hovedpersonen, medmindre det udtrykkeligt er flyttet andre steder. Task Task.detached Task.detached opretter en helt uafhængig opgave. Den arver ikke den aktuelle aktørkontekst eller prioritet. Dette betyder, at den starter i en global samtidig kontekst og kræver styring af sikkerhed, især når du får adgang til delte data. Task.detached Task.detached { await performBackgroundWork() } Use Når du har brug for at køre baggrundsoperationer uden for den nuværende strukturerede kontekst, såsom langvarige beregninger eller undslippe en skuespiller isolation. Task.detached Samarbejde med Thread Pool En Cooperative Thread Pool i Swift Concurrency er en mekanisme, der styrer udførelsen af asynkrone opgaver ved at planlægge dem på et begrænset antal tråde, typisk matcher antallet af CPU-kerner. A Cooperative Thread Pool in Swift Concurrency is a mechanism that manages the execution of asynchronous tasks by scheduling them onto a limited number of threads, typically matching the number of CPU cores. Swift Concurrency opererer ved hjælp af et kooperativt thread pool designet til effektiv planlægning og minimal thread overhead. I modsætning til den traditionelle thread-per-task-eksekveringsmodel lægger Swift's tilgang vægt på struktureret concurrency og ressource-bevidst planlægning. En almindelig overforenkling er at sige, at Swift Concurrency bruger en tråd pr. Kernel, som er i overensstemmelse med sit mål om at reducere kontekstuelle skift og maksimere CPU-udnyttelsen. Thread Count: Ikke så simpelt Thread Count: Not So Simple On a 16-core Mac, it’s possible to observe up to 64-threads managed by Swift Concurrency alone - without GCD involvement. This is because Swift’s cooperative thread pool maps not just per-core, but per core per QoS bucket. Det formelle: Max threads = (CPU cores) × (dedicated quality-of-service buckets) Thus, on a 16-core system: 16 cores × 4 QoS buckets = 64 threads Hver QoS bucket er i det væsentlige en dedikeret thread lane for en gruppe af opgaver, der deler lignende udførelsesprioritet. Disse styres internt af Darwins thread scheduling mekanisme og er ikke det samme som GCD-kæder. QoS Buckets og Task Priority QoS Buckets og Task Priority Selvom Eksponerer seks konstanter, hvoraf nogle er aliaser: TaskPriority Begyndt med høj Nyttigheden er lav Default → allerede kortlagt til medium For kernelperspektivet forenkler dette til 4 kerneprioriteringsniveauer, som hver er kortlagt til en QoS-bucket, hvilket påvirker trådeallokationen i det kooperative thread pool. Hvornår sker overforpligtelse? Hvornår sker overforpligtelse? Under normal belastning respekterer Swift Concurrency samarbejdspoolgrænserne. Men under stridigheder (f.eks. højprioriterede opgaver, der venter på lavprioriterede opgaver) kan systemet overarbejde tråde for at bevare responsivitet. Denne dynamiske justering sikrer, at tidssensitive opgaver ikke blokeres på ubestemt tid bag lavprioriteret arbejde. Denne adfærd styres af Darwin-kernen via Mach-planlægningspolitikker og højprioritet linjer - ikke noget, der udtrykkeligt styres af din kode. pthreads Prioriteret opgave Swift giver et prioriteringssystem til opgaver, der ligner Grand Central Dispatch (GCD), men som er mere semantisk integreret i den strukturerede concurrency-model. Den indledende: Task Task(priority: .userInitiated) { await loadUserData() } The available priorities are defined by the af Enum: TaskPriority Priority Description / .high .userInitiated For tasks initiated by user interaction that require immediate feedback. .medium For tasks that the user is not actively waiting for. / .low .utility For long-running tasks that don’t require immediate results, such as copying files or importing data. .background For background tasks that the user is not directly aware of. Primarily used for work the user cannot see. / af .high .userInitiated Til opgaver initieret af brugerinteraktion, der kræver øjeblikkelig feedback. .medium For opgaver, som brugeren ikke aktivt venter på. / af .low .utility Til langvarige opgaver, der ikke kræver øjeblikkelige resultater, f.eks. kopiering af filer eller import af data. .background For baggrundsopgaver, som brugeren ikke er direkte opmærksom på. primært bruges til arbejde, som brugeren ikke kan se. At skabe opgaver med forskellige prioriteter At skabe opgaver med forskellige prioriteter Når du opretter en Inden for en anden opgave (default prioritering), kan du udtrykkeligt angive en anden prioritet for hver indlejret opgave. , og den anden er .high. Dette viser, at prioriteter kan indstilles individuelt uafhængigt af forælderen. Task .medium .low Task { // .medium by default Task(priority: .low) { print("\(1), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } Task(priority: .high) { print("\(2), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } } // 1, thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}, priority: TaskPriority.low // 2, thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}, priority: TaskPriority.high Hvis du ikke udtrykkeligt angiver en prioritet for en indlejret opgave, arver den prioritet fra den umiddelbare forælder. og blocks inherit those respective priorities unless overridden. .high .low Arbejdsprioriteter kan arves Arbejdsprioriteter kan arves Task { Task(priority: .high) { Task { print("\(1), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } } Task(priority: .low) { print("\(2), "thread: \(Thread.current)", priority: \(Task.currentPriority)") Task { print("\(3), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } Task(priority: .medium) { print("\(4), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } } } // 2, thread: <_NSMainThread: 0x600001708040>{number = 1, name = main}, priority: TaskPriority.low // 1, thread: <_NSMainThread: 0x600001708040>{number = 1, name = main}, priority: TaskPriority.high // 3, thread: <_NSMainThread: 0x600001708040>{number = 1, name = main}, priority: TaskPriority.low // 4, thread: <_NSMainThread: 0x600001708040>{number = 1, name = main}, priority: TaskPriority.medium Hvis du ikke udtrykkeligt angiver en prioritet for en indlejret opgave, arver den prioriteten fra den umiddelbare forælder. I dette eksempel arver de anonyme opgaver i .high og .low blokke de respektive prioriteter, medmindre de overdrives. Prioritering af eskalering Prioritering af eskalering Task(priority: .high) { Task { print("\(1), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } await Task(priority: .low) { print("\(2), "thread: \(Thread.current)", priority: \(Task.currentPriority)") await Task { print("\(3), "thread: \(Thread.current)", priority: \(Task.currentPriority)") }.value Task(priority: .medium) { print("\(4), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } }.value } // 1, thread: <_NSMainThread: 0x6000017000c0>{number = 1, name = main}, priority: TaskPriority.high // 2, thread: <_NSMainThread: 0x6000017000c0>{number = 1, name = main}, priority: TaskPriority.high // 3, thread: <_NSMainThread: 0x6000017000c0>{number = 1, name = main}, priority: TaskPriority.high // 4, thread: <_NSMainThread: 0x6000017000c0>{number = 1, name = main}, priority: TaskPriority.medium Denne mekanisme kaldes prioritetsescalation – når en opgave venter på en opgave med højere prioritet, kan systemet midlertidigt hæve sin prioritet for at undgå flaskehalse og sikre responsivitet. Som et resultat: Opgave 2, som er .low, eskaleres til .high, mens den venter. Task 3, which doesn’t have an explicit priority, inherits the escalated priority from its parent (Task 2) and is also executed with priority. .high Opgave 4 prioriterer udtrykkeligt .medium, så det ikke påvirkes af eskalering. Arver ikke prioritet Task.detached Task.detached Uddrag af opgaver ( De opfører sig som globale opgaver med deres egen planlægning. Dette er nyttigt til at isolere baggrundsarbejde, men kan også føre til uventede prioriteringsforskelle, hvis de ikke indstilles manuelt. Task.detached Task(priority: .high) { Task.detached { print("\(1), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } Task(priority: .low) { print("\(2), "thread: \(Thread.current)", priority: \(Task.currentPriority)") Task.detached { print("\(3), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } Task(priority: .medium) { print("\(4), "thread: \(Thread.current)", priority: \(Task.currentPriority)") } } } // 1, thread: <NSThread: 0x60000174dec0>{number = 4, name = (null)}, priority: TaskPriority.medium // 2, thread: <_NSMainThread: 0x600001708180>{number = 1, name = main}, priority: TaskPriority.low // 3, thread: <NSThread: 0x60000174dec0>{number = 4, name = (null)}, priority: TaskPriority.medium // 4, thread: <_NSMainThread: 0x600001708180>{number = 1, name = main}, priority: TaskPriority.medium Suspension Points og hvordan Swift håndterer Async-udførelse In Swift, any call to an Funktioner ved brug af er et potentielt suspensionspunkt - et sted i funktionen, hvor udførelsen kan pause og genoptage sidstnævnte. async await Here’s an example: func fetchData() async -> String { let result = await networkClient.load() return result } I dette tilfælde, Når funktionen når denne linje, kan den pause udførelsen, give kontrol til systemet og senere genoptage en gang. Bag scenerne omdanner compilatoren denne funktion til en tilstandsmaskine, der sporer dens fremskridt og interne variabler. await networkClient.load() load() Under the Hood: Continuations and State Machines Enhver Funktionen i Swift er kompileret til en statemaskine. markerer et overgangspunkt. Før du når et af Swift: async await await Gemmer den aktuelle tilstand af funktionen - herunder lokale variabler og den aktuelle instruktionspointer. Suspender udførelsen og planlægger en fortsættelse. Once the async operation completes, it resumes the function from where it left off. Dette ligner den fortsættelsespassende stil (CPS), der anvendes i mange funktionelle programmeringssystemer.I Swifts concurrency-model er dette orchestreret af interne typer som and the concurrency runtime scheduler. ParticialAsyncTask Suspension != Blocking Når du i Swift, den aktuelle tråd er ikke blokeret, i stedet: await Den aktuelle opgave giver kontrol tilbage til eksekutøren Andre opgaver kan løbe mens du venter Når den forventede operation er afsluttet, genoptages den suspenderede opgave på den relevante udfører. Dette gør grundlæggende mere effektiv og skalerbar end trådbaserede blokeringsoperationer som . async/await DispatchQueue.sync • Lade andre opgaver køre Task.Yield er ( Task.Yield er ( er en statisk metode, der leveres af Swift's concurrency-system, som frivilligt suspenderer den aktuelle opgave, hvilket giver systemet mulighed for at køre andre enqueuede opgaver. Task.yield() func processLargeBatch() async { for i in 0..<1_000_000 { if i % 10_000 == 0 { await Task.yield() } } } Without Dette vil monopolisere udøveren ved at indsætte Periodisk samarbejder du med Swifts concurrency runtime, så du kan opretholde responsivitet og retfærdighed. await await Task.yield() Under Hood Venter på opkald suspenderer den aktuelle opgave og genindkøber den ved afslutningen af køen for den aktuelle udfører (f.eks. hovedaktør eller global samtidig udfører). Task.yield() Det er en del af Swifts kooperative multitasking-model: opgaver kører til næste suspensionspunkt og forventes at levere retfærdigt.I modsætning til forebyggende systemer (f.eks. tråde) bliver Swift-opgaver ikke tvunget til at blive afbrudt – de skal frivilligt give kontrol. Sammendrag Swift 6 markerer et betydeligt skridt fremad i, hvordan concurrency håndteres, og tilbyder udviklere mere kontrol, forudsigelighed og sikkerhed. Mens læringskurven kan være stejl i starten, åbner forståelsen af disse begreber døren til at opbygge meget responsive og robuste applikationer.