Swift 6 introduciu un novo enfoque para a concorrencia en aplicacións. Neste artigo, exploraremos os problemas que pretende resolver, explicar como funciona baixo o capó, comparar o novo modelo co anterior e botar unha ollada máis de preto ao modelo Actor. Nas próximas partes, tamén descompoñeremos executores, planificadores, concorrencia estruturada, diferentes tipos de executores, implementar o noso propio executor e moito máis. Swift Concurrency Overview: Problemas e solucións Concurrency has long been one of the most challenging aspects of software development. Writing code that runs tasks simultaneously can improve performance and responsiveness, but it often introduces complexity and subtle bugs as race conditions, deadlocks, and thread-safety issues. Swift Concurrency, introducido en Swift 6, ten como obxectivo simplificar a programación simultánea proporcionando un modelo claro, seguro e eficiente para xestionar tarefas asíncronas. 💡Se usa Swift 5.x e planea migrar a Swift 6, pode habilitar as verificacións de Concurrencia de Swift nas súas configuracións de proxecto. Isto permítelle adoptar gradualmente o novo modelo de concorrencia mantendo a compatibilidade co código existente. Activando estas verificacións axuda a capturar os posibles problemas de concorrencia de forma precoz, facendo a transición máis suave e segura. A medida que actualiza a súa base de códigos, pode comezar a integrar a sintaxe async/await e outras características de Concurrencia de Swift incrementalmente sen unha reescritura completa. 💡Se usa Swift 5.x e planea migrar a Swift 6, pode habilitar os controis de concorrencia de Swift nas configuracións do seu proxecto. Isto permítelle adoptar gradualmente o novo modelo de concorrencia mantendo a compatibilidade co código existente. async/await sintaxe e outras funcións de Swift Concurrency incrementalmente sen unha reescritura completa. Some of the key problems Swift Concurrency addresses include: Condicións de raza: Impedir o acceso simultáneo ao estado mutábel compartido que poida causar comportamentos imprevisibles. Callback hell: Simplifica o código asíncrono que adoitaba depender en gran medida de chamadas de volta ou procesadores de finalización, facendo que o código sexa máis doado de ler e manter. Complexidade de xestión de thread: Abstracción de creación e sincronización de baixo nivel, permitindo aos desenvolvedores centrarse na lóxica en vez de manexar o thread. Coordinación de tarefas simultáneas: A concorrencia estruturada permite xerarquías claras de tarefas con correcta cancelación e propagación de erros. Coa incorporación de novas linguas como Swift 6 ofrece unha forma máis intuitiva e robusta de escribir código simultáneo, mellorando tanto a produtividade do desenvolvedor como a estabilidade da aplicación. async/await Multitasking Os sistemas operativos modernos e os tempos de execución utilizan o multitasking para executar unidades de traballo simultaneamente. Swift Concurrency adopta multitasking cooperativo, que difire fundamentalmente do modelo de multitasking preemptivo utilizado polos fíos de nivel OS. Entender a diferenza é clave para escribir código Swift asíncrono de alto rendemento e seguro. Multitasking preventivo Preemptive multitasking is the model used by operating systems to manage threads and processes. In this model, a system-level scheduler can forcibly interrupt any thread at virtually any moment to perform a context switch and allocate CPU time to another thread. This ensures fairness across the system and allows for responsive applications — especially when handling multiple user-driven or time-sensitive tasks. O multitarefa preventiva permite un verdadeiro paralelismo entre varios núcleos de CPU e evita que os fíos mal comportados ou de longa duración monopolicen os recursos do sistema. Con todo, esta flexibilidade vén a un custo. Debido a que os fíos poden ser interrompidos en calquera momento da súa execución - mesmo no medio dunha operación crítica - os desenvolvedores deben usar primitivos de sincronización como mutexes, semáforos ou operacións atómicas para protexer o estado mutábel compartido. Este modelo ofrece un maior control e concorrencia, pero tamén pon unha carga significativamente maior sobre os desenvolvedores.Asegurar a seguridade do fío nun ambiente preventivo é propenso a erros e pode levar a un comportamento non determinista - comportamento que varía de execución a execución - que é notoriamente difícil de razoar ou probar de forma fiable. Desde un punto de vista técnico, o multitasking preemptivo depende do sistema operativo para manexar a execución de fíos. O sistema operativo pode interromper un fío en case calquera punto - mesmo no medio dunha función - e cambiar a outro. Para iso, o sistema debe realizar un switch de contexto, o que implica gardar o estado de execución completo do fío actual (como os rexistros da CPU, o ponteiro de instrucións e o ponteiro de pilas), e restaurar o estado previamente gardado doutro fío. Estas operacións introducen un tempo de execución significativo. Cada interruptor de contexto leva tempo e consome recursos do sistema - especialmente cando os interruptores de contexto son frecuentes ou cando moitos fios compiten por núcleos de CPU limitados. Aínda que este modelo ofrece máxima flexibilidade e verdadeiro paralelismo, a miúdo é excesivo para fluxos de traballo asíncronos, onde as tarefas normalmente pasan a maior parte do seu tempo agardando por I/O, entrada de usuario ou respostas de rede en lugar de usar activamente a CPU. Multitasking cooperativo En contraste, o tempo de execución concurrente de Swift utiliza multitasking cooperativo. Neste modelo, unha tarefa corre ata que voluntariamente dá o control - tipicamente nun punto de espera ou a través dunha chamada explícita a A diferenza das cadeas tradicionais, as tarefas cooperativas nunca son preimpostas pola forza, o que resulta nunha execución previsible: os cambios de contexto ocorren só en puntos de suspensión claramente definidos. Task.yield() As tarefas de cooperación de Swift están programadas para un conxunto de fíos cooperativos lixeiros e xestionados en tempo de execución, separados das filas de Grand Central Dispatch. As tarefas que se executan neste conxunto esperan ser "boos cidadáns", proporcionando o control cando sexa necesario, especialmente durante o traballo de execución prolongada ou intensivo en CPU. como punto de suspensión manual, asegurando que outras tarefas teñan unha oportunidade de executar. Task.yield() Non obstante, o multitarefa cooperativa vén cunha advertencia: se unha tarefa nunca se suspende, pode monopolizar o fío que está executando, atrasando ou afogando outras tarefas no sistema. En multitasking cooperativo, a unidade fundamental de execución non é un fío, senón un anaco de traballo, a miúdo chamado unha continuación. Funcións suspendidas en , o tempo de execución Swift captura o estado de execución actual nunha continuidade asignada por pila. Esta continuidade representa un punto de reanudación e está enqueada para a futura execución. async await No canto de asociar un thread a unha tarefa de longa duración, o tempo de execución Swift trata un thread como unha condución de continuacións. Cada thread executa unha continuación tras outra. Cando unha continuación remata ou se suspende de novo, o thread recolle a seguinte continuación preparada da cola. Como se mencionou anteriormente, este modelo evita os interruptores de contexto tradicionais a nivel de sistema operativo. Non hai necesidade de gardar e restaurar rexistros de CPU ou pilas de fíos; o tempo de execución simplemente invoca a seguinte continuidade de peche. Isto fai que o cambio de tarefas sexa moi rápido e lixeiro, aínda que implica asignacións de pila aumentadas para almacenar o estado asíncope suspendido. O compromiso clave: usa un pouco máis de memoria pero gaña drasticamente menos superficies para a xestión de tarefas. a planificación cooperativa dá un control estrito sobre cando ocorren suspensións, o que mellora a previsibilidade e fai que a concorrencia sexa máis fácil de razoar. Introdución ao Tarefas Tarefas En competición rápida, a proporciona unha unidade de traballo asíncrono. a diferenza de simplemente chamar un funcións, a é un obxecto xestionado que se executa de forma simultánea con outras tarefas nun grupo de thread cooperativo. Task async Task 💡 As tarefas son xestionadas por un conxunto de fíos cooperativos. O conxunto de fíos cooperativos está deseñado para xestionar a concorrencia de forma eficiente permitindo que as tarefas produzan a CPU mentres esperan que as operacións asíncronas se complete. Isto é logrado mediante o uso de funcións e tarefas asíncronas, que son as unidades fundamentais de concorrencia en Swift. 💡 As tarefas son xestionadas por un conxunto de fíos cooperativos. O conxunto de fíos cooperativos está deseñado para xestionar a concorrencia de forma eficiente permitindo que as tarefas produzan a CPU mentres esperan que as operacións asíncronas se complete. Isto é logrado mediante o uso de funcións e tarefas asíncronas, que son as unidades fundamentais de concorrencia en Swift. As tarefas poden ser creadas para ser executadas simultaneamente, e tamén poden ser agardadas ou canceladas.Proporcionan un control fino sobre o comportamento asíncrono e son unha parte integral da concorrencia estruturada en Swift. Creación a Task A task can be created using the , que inmediatamente inicia a operación asíncrona proporcionada: Task Iniciación Iniciación Task(priority: .userInitiated) { await fetchData() } Cando creas unha utilización do inicializador estándar (é dicir, non ), herda o contexto, a prioridade e os valores locais de tarefas do actor circundante.Este comportamento é crucial para a concorrencia estruturada e a seguridade no código concurrente. Task desprazados desprazados 💡 Swift 6.2 introduce un cambio significativo na forma en que se xestiona a concorrencia: por defecto, todo o código é executado en MainActor. Para executar o código en segundo plano, Swift 6.2 engade un novo atributo @concurrent. Tamén pode usar non illado se o código non require acceso ao actor principal. Swift 6.2 introduce un cambio significativo na forma en que se xestiona a concorrencia: por defecto, todo o código é executado en MainActor Para executar código en segundo plano, Swift 6.2 engade un novo atributo @concurrent Tamén podes usar nonisolated se o código non require o acceso ao actor principal. WWDC Embracing Swift WWDC incorpora a Swift á competencia WWDC incorpora a Swift á competencia Baixo o capó, en versións anteriores de Swift Concurrency, o tempo de execución de Swift usou un mecanismo interno chamado Aínda que esta propiedade non formaba parte da API pública, desempeñou un papel clave en asegurar que as tarefas creadas dentro do código illado polo actor se executaran no mesmo actor, preservando a seguridade da carreira de datos. @_inheritActorContext Con avances en Swift, o tempo de execución comezou a pasar de Un novo sistema coñecido como , which is now more explicitly handled by the compiler and runtime. @_inheritActorContext sending Enviar é unha nova palabra clave introducida en Swift 6 como parte do movemento da linguaxe cara a concorrencia máis segura e explícita. Úsase para marcar parámetros de función e devolver valores que se moven a través de límites concorrenciais. Funciona especialmente ben tipos non copiables, garantindo a seguridade da memoria e evitando erros de desprazamento despois do uso. Cando un parámetro está marcado con enviar, o compilador obriga a que a instancia orixinal xa non se acceda despois da transferencia. func process(_ data: sending MyNonCopyableType) async { // `data` is moved here and can’t be used elsewhere after the call } Enviar é unha nova palabra clave introducida en Swift 6 como parte do movemento da linguaxe cara a concorrencia máis segura e explícita. Úsase para marcar parámetros de función e devolver valores que se moven a través de límites concorrenciais. Funciona especialmente ben tipos non copiables, garantindo a seguridade da memoria e evitando erros de desprazamento despois do uso. Cando un parámetro está marcado con enviar, o compilador obriga a que a instancia orixinal xa non se acceda despois da transferencia. func process(_ data: sending MyNonCopyableType) async { // `data` is moved here and can’t be used elsewhere after the call } Cando se lanza unha , ten que asegurarse de que calquera valores capturados pola tarefa son O compilador agora fai cumprir isto no tempo de compilación usando protocol and the Tipo de función. Task.detached Sendable Sendable @Sendable incapacidade de conformarse Pode producirse un erro de tempo de compilación, particularmente en modo de concorrencia estricta. Sendable As tarefas tamén soportan prioridades, semellante a como as filas de Grand Central Dispatch as manexan. vs Task Task.detached Ao traballar con Swift Concurrency, é importante comprender a diferenza entre e , xa que definen como e onde se executa o traballo asíncrono. Task Task.detached Task Tarefas herdan o contexto actual do actor (como ou calquera actor personalizado) e prioridade. Utilízase comunmente cando desexa engendrar unha nova operación asíncrona que aínda respecte a árbore de concorrencia estruturada actual ou o illamento do actor. Task MainActor Task { await updateUI() } No exemplo anterior, se se chama desde o actor principal, o Tamén actuará sobre o actor principal a non ser que se mova explicitamente a outro lugar. Task Task.detached Descargar Descargar Crea unha tarefa completamente independente. Non herda o contexto ou prioridade actual do actor. Isto significa que comeza nun contexto simultáneo global e require xestionar a seguridade, especialmente cando se accede a datos compartidos. Task.detached Task.detached { await performBackgroundWork() } Utilización Cando é necesario executar operacións de fondo fóra do contexto estruturado actual, como cálculos de longa duración ou escapar do illamento dun actor. Task.detached Cooperative Thread Pool A Cooperative Thread Pool en Swift Concurrency é un mecanismo que xestiona a execución de tarefas asíncronas agardándoas nun número limitado de threads, normalmente coincidindo co número de núcleos de CPU. A Cooperative Thread Pool en Swift Concurrency é un mecanismo que xestiona a execución de tarefas asíncronas agardándoas nun número limitado de threads, normalmente coincidindo co número de núcleos de CPU. Swift Concurrency opera usando un conxunto cooperativo de enlaces deseñado para unha planificación eficiente e un superávit mínimo de enlaces. A diferenza do tradicional modelo de execución de enlaces por tarefas, o enfoque de Swift enfatiza a concorrencia estruturada e a planificación consciente dos recursos. Unha supersimplificación común é dicir que Swift Concurrency usa un fío por núcleo, que se alinea co seu obxectivo de reducir o cambio de contexto e maximizar a utilización da CPU. Título: Non tan sinxelo Título: Non tan sinxelo Nun Mac de 16 núcleos, é posible observar ata 64 filamentos xestionados por Swift Concurrency só - sen a implicación de GCD. Isto é porque o grupo de filamentos cooperativo de Swift non só mapea por núcleo, senón por núcleo por bucket de QoS. de xeito formal: Max threads = (CPU cores) × (dedicated quality-of-service buckets) Así, nun sistema de 16 núcleos: 16 cores × 4 QoS buckets = 64 threads Each QoS bucket is essentially a dedicated thread lane for a group of tasks sharing similar execution priority. These are managed internally by Darwin’s thread scheduling mechanism and are not the same as GCD queues. QoS Buckets e prioridade de tarefas QoS Buckets e prioridade de tarefas Aínda que Expoñen seis constantes, algunhas delas son aliases: TaskPriority Iniciación ao alto → utility low → xa mapeado a medio Para a perspectiva do núcleo, isto simplifica a 4 niveis de prioridade de núcleo, cada un mapeado a un bucket de QoS, o que inflúe na asignación de thread no pool de thread cooperativo. When Does Overcommit Happen? Cando ocorre o sobrecompromiso? Baixo a carga normal, Swift Concurrency respecta os límites do pool cooperativo. Con todo, baixo contencións (por exemplo, tarefas de alta prioridade agardando por tarefas de baixa prioridade), o sistema pode sobrecomprometerse para preservar a capacidade de resposta. Este axuste dinámico asegura que as tarefas sensibles ao tempo non estean bloqueadas indefinidamente detrás do traballo de menor prioridade. Este comportamento é xestionado polo núcleo Darwin a través de políticas de planificación de Mach e de alta prioridade. liñas - non algo controlado explicitamente polo seu código. pthreads Prioridade de tarefas Swift provides a priority system for tasks, similar to Grand Central Dispatch (GCD), but more semantically integrated into the structured concurrency model. You can set a task’s priority via the Iniciación ao: Task Task(priority: .userInitiated) { await loadUserData() } As prioridades dispoñibles están definidas polo 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. / de .high .userInitiated Para tarefas iniciadas pola interacción do usuario que requiren un feedback inmediato. .medium Para tarefas que o usuario non está a esperar activamente. / de .low .utility Para tarefas de longa duración que non requiren resultados inmediatos, como copiar ficheiros ou importar datos. .background Para tarefas de fondo que o usuario non é directamente consciente. Creación de tarefas con diferentes prioridades Creación de tarefas con diferentes prioridades Cando creas unha dentro doutra tarefa (default prioridade), pode definir explicitamente unha prioridade diferente para cada tarefa. , e a outra é .high. Isto demostra que as prioridades poden ser establecidas individualmente independentemente do pai. 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 Se non se establece explicitamente unha prioridade para unha tarefa anexada, ela herda a prioridade da súa nai inmediata. e Os bloques herdan esas prioridades respectivas a non ser que sexan superados. .high .low As prioridades da tarefa poden ser herdadas As prioridades da tarefa poden ser herdadas 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 Se non establece explicitamente unha prioridade para unha tarefa anexada, ela herdará a prioridade da súa nai inmediata. Neste exemplo, as tarefas anónimas dentro dos bloques .high e .low herdarán esas prioridades respectivas a menos que sexan superadas. A escalada da prioridade Task Priority Escalation 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 Este mecanismo chámase escalada de prioridade: cando unha tarefa está agardando por unha tarefa de maior prioridade, o sistema pode aumentar temporalmente a súa prioridade para evitar obstáculos e garantir a capacidade de resposta. Como resultado: A tarefa 2, que é .low, escálase a .high mentres se espera. Tarefa 3, que non ten unha prioridade explícita, herda a prioridade escalada da súa prioridade principal (Tarefa 2) e tamén se executa con .highpriority. A tarefa 4 establece explicitamente a súa prioridade a .medium, polo que non é afectada pola escalada. Non herdan unha prioridade Task.detached Descargar Descargar Tarefas separadas ( Executen de forma independente e non herdan a prioridade da súa tarefa principal. Comportanse como tarefas globais coa súa propia programación. Isto é útil para illar o traballo de fondo, pero tamén pode levar a diferenzas de prioridades inesperadas se non se establece manualmente. 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 Puntos de suspensión e como Swift xestiona a execución de Async En Swift, calquera chamada a un Funcións Usando is a potential suspension point - a place in the function where executing might pause and resume latter. It’s a transformation that involves saving the state of the function so it can be resumed latter, after awaited operation completes. async await Here’s an example: func fetchData() async -> String { let result = await networkClient.load() return result } Neste caso, Cando a función alcanza esta liña, pode pausar a execución, render control ao sistema e posteriormente retomar unha vez. Detrás das escenas, o compilador transforma esta función nunha máquina de estado que rastrexa o seu progreso e as súas variables internas. await networkClient.load() load() Baixo o casco: continuacións e máquinas de estado cada unha A función en Swift está compilada nunha máquina de estado. que marca un punto de transición. antes de chegar a un A súa rapidez: async await await Saves the current state of the function - including local variables and the current instruction pointer. Suspends execution and schedules a continuation. Unha vez completada a operación async, retoma a función desde onde deixou de funcionar. Isto é similar ao estilo de paso de continuidade (CPS) utilizado en moitos sistemas de programación funcional. No modelo de concorrencia de Swift, isto está orquestrado por tipos internos como e o calendario de competición. ParticialAsyncTask Suspensión! = Bloqueo cando ti algo en Swift, o thread actual non está bloqueado, en vez diso: await A tarefa actual devolve o control ao executor Other tasks can run while waiting Cando se completa a operación esperada, a tarefa suspendida continúa no executor correspondente. Isto fai fundamentalmente máis eficiente e escalable que as operacións de bloqueo baseadas en fíos como . async/await DispatchQueue.sync : Deixar executar outras tarefas Tarefa.proxecto() Tarefa.proxecto() É un método estático proporcionado polo sistema de concorrencia de Swift que suspende voluntariamente a tarefa actual, dándolle ao sistema a oportunidade de executar outras tarefas enqueadas. Task.yield() func processLargeBatch() async { for i in 0..<1_000_000 { if i % 10_000 == 0 { await Task.yield() } } } sen esta liña monopolizaría o executor. periodicamente, está a cooperar co tempo de execución concorrente de Swift, permitindo manter a resposta e a equidade. await await Task.yield() Under the Hood Calling await Suspende a tarefa actual e repártea ao final da cola para o seu executor actual (por exemplo, un actor principal ou un executor simultáneo global). Task.yield() É parte do modelo de multitarea cooperativa de Swift: as tarefas corren ata o seguinte punto de suspensión e espérase que obteñan un rendemento xusto. A diferenza dos sistemas preventivos (por exemplo, os feitos), as tarefas de Swift non se interrompen forzadamente - deben render voluntariamente o control. Resumo Swift 6 marca un paso importante cara a como se xestiona a concorrencia, ofrecendo aos desenvolvedores máis control, predictibilidade e seguridade.Aínda que a curva de aprendizaxe pode ser áspera ao principio, a comprensión destes conceptos abre a porta para a construción de aplicacións altamente responsivas e robustas.Como continuamos explorando os aspectos máis profundos do novo modelo de concorrencia de Swift, está claro que estes cambios poñen as bases para o futuro do desenvolvemento de aplicacións seguras e escalables.