0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Swift 的并发系统并行运行多个任务

来源:jf_57394773 作者:jf_57394773 2025-11-11 11:33 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

​​前言
Swift 内置并发系统的好处之一是它可以更轻松地并行执行多个异步任务,这反过来又可以使我们显着加快可以分解为单独部分的操作。

在本文中,让我们看一下几种不同的方法,以及这些技术中的每一种何时特别有用。

从异步到并发
首先,假设我们正在开发某种形式的购物应用程序来显示各种产品,并且我们已经实现了一个ProductLoader允许我们使用一系列异步 API 加载不同产品集合的应用程序,如下所示:

class ProductLoader {
   ...

   func loadFeatured() async throws -> [Product] {
       ...
   }
   
   func loadFavorites() async throws -> [Product] {
       ...
   }
   
   func loadLatest() async throws -> [Product] {
       ...
   }
}
AI写代码
尽管大多数情况下上述每个方法都可能会被单独调用,但假设在我们应用程序的某些部分中,我们还希望形成一个Recommendations包含这三个ProductLoader方法的所有结果的组合模型:
extension Product {
   struct Recommendations {
       var featured: [Product]
       var favorites: [Product]
       var latest: [Product]
   }
}
AI写代码
一种方法是使用await关键字调用每个加载方法,然后使用这些调用的结果来创建我们Recommendations模型的实例——如下所示:
extension ProductLoader {
   func loadRecommendations() async throws -> Product.Recommendations {
       let featured = try await loadFeatured()
let favorites = try await loadFavorites()
let latest = try await loadLatest()
       
       return Product.Recommendations(
           featured: featured,
           favorites: favorites,
           latest: latest
       )
   }
}
AI写代码
上面的实现确实有效——然而,即使我们的三个加载操作都是完全异步的,它们目前正在按顺序执行,一个接一个。因此,尽管我们的顶级loadRecommendations方法相对于我们应用程序的其他代码正在并发执行,但实际上它还没有利用并发来执行其内部操作集。
由于我们的产品加载方法不以任何方式相互依赖,因此实际上没有理由按顺序执行它们,所以让我们看看如何让它们完全同时执行。
关于如何做到这一点的初步想法可能是将上述代码简化为单个表达式,这将使我们能够使用单个await关键字来等待我们的每个操作完成:
extension ProductLoader {
   func loadRecommendations() async throws -> Product.Recommendations {
       try await Product.Recommendations(
           featured: loadFeatured(),
           favorites: loadFavorites(),
           latest: loadLatest()
       )
   }
}
AI写代码
然而,即使我们的代码现在看起来是并发的,它实际上仍会像以前一样完全按顺序执行。
相反,我们需要利用 Swift 的async let绑定来告诉并发系统并行执行我们的每个加载操作。使用该语法使我们能够在后台启动异步操作,而无需我们立即等待它完成。
await如果我们在实际使用加载的数据时(即形成模型时)将其与单个关键字组合Recommendations,那么我们将获得并行执行加载操作的所有好处,而无需担心状态管理或数据竞争之类的事情:
extension ProductLoader {
   func loadRecommendations() async throws -> Product.Recommendations {
       async let featured = loadFeatured()
async let favorites = loadFavorites()
async let latest = loadLatest()
       
       return try await Product.Recommendations(
           featured: featured,
           favorites: favorites,
           latest: latest
       )
   }
}
AI写代码
很整齐!因此async let,当我们有一组已知的、有限的任务要执行时,它提供了一种同时运行多个操作的内置方法。但如果不是这样呢?
任务组
现在假设我们正在开发一个ImageLoader可以让我们通过网络加载图像的工具。要从给定的 加载单个图像URL,我们可以使用如下所示的方法:
class ImageLoader {
   ...

   func loadImage(from url: URL) async throws -> UIImage {
       ...
   }
}
AI写代码
为了使一次加载一系列图像变得简单,我们还创建了一个方便的 API,它接受一个 URL 数组并异步返回一个图像字典,该字典由下载图像的 URL 键控:
extension ImageLoader {
   func loadImages(from urls: [URL]) async throws -> [URL: UIImage] {
       var images = [URL: UIImage]()
       
       for url in urls {
           images[url] = try await loadImage(from: url)
       }
       
       return images
   }
}
AI写代码
现在让我们说,就像我们ProductLoader之前的工作一样,我们想让上面的loadImages方法并发执行,而不是按顺序下载每个图像(目前是这种情况,因为我们await在调用时直接使用loadImage我们的for环形)。
但是,这次我们将无法使用async let,因为我们需要执行的任务数量在编译时是未知的。值得庆幸的是,Swift 并发工具箱中还有一个工具可以让我们并行执行动态数量的任务——任务组。
要形成一个任务组,我们可以调用withTaskGroup或withThrowingTaskGroup,这取决于我们是否希望可以选择在我们的任务中抛出错误。在这种情况下,我们将选择后者,因为我们的底层loadImage方法是用throws关键字标记的。
然后我们将遍历每个 URL,就像以前一样,只是这次我们将每个图像加载任务添加到我们的组中,而不是直接等待它完成。相反,我们将await在添加每个任务之后单独分组结果,这将允许我们的图像加载操作完全并发执行:
extension ImageLoader {
   func loadImages(from urls: [URL]) async throws -> [URL: UIImage] {
       try await withThrowingTaskGroup(of: (URL, UIImage).self) { group in
           for url in urls {
               group.addTask{
   let image = try await self.loadImage(from: url)
   return (url, image)
}
           }
           
           var images = [URL: UIImage]()
           
           for try await (url, image) in group {
   images[url] = image
}
           
           return images
       }
   }
}
AI写代码

要了解有关上述for try await语法和一般异步序列的更多信息,请查看“异步序列、流和组合”。

就像使用 时一样async let,以我们的操作不会直接改变任何状态的方式编写并发代码的一个巨大好处是,这样做可以让我们完全避免任何类型的数据竞争问题,同时也不需要我们引入任何锁定或序列化代码混合在一起。

await因此,在可能的情况下,让我们的每个并发操作返回一个完全独立的结果,然后依次返回这些结果以形成我们的最终数据集,这通常是一种很好的方法。

在以后的文章中,我们将更仔细地研究避免数据竞争的其他方法(例如通过使用 Swift 的新actor类型)。

结论
重要的是要记住,仅仅因为给定的函数被标记为async并不一定意味着它同时执行它的工作。相反,如果这是我们想要做的,我们必须故意让我们的任务并行运行,这只有在执行一组可以独立运行的操作时才有意义。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • API
    API
    +关注

    关注

    2

    文章

    2515

    浏览量

    67177
  • SWIFT
    +关注

    关注

    0

    文章

    125

    浏览量

    24891
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Java并发编程的“基石”——多线程概念初识

    。面对每秒涌入的数万个算力请求,Java 提供了极其丰富的并发容器和同步工具。无论是处理任务排队的无锁队列,还是控制多个调度Worker并行计算“最优装箱算法”的同步屏障,Java
    发表于 04-16 18:50

    寻找对RISCV众核并行计算感兴趣的伙伴、朋友

    和旗语等方式。 二、此方案适用于RISCV+AI;低功耗、低延迟、高并发场合;硬件仿真加速芯片方案,可以并行运行verilog的initial/always进程;以及其它并行场景。 三、此方案的特点有
    发表于 03-28 14:41

    寻找对RISCV众核并行计算感兴趣的伙伴

    和旗语等方式。 二、此方案适用于RISCV+AI;低功耗、低延迟、高并发场合;硬件仿真加速芯片方案,可以并行运行verilog的initial/always进程;以及其它并行场景。 三、此方案的特点有
    发表于 03-28 14:37

    在睿思芯科灵羽RISC-V服务器CPU实现多实例OpenClaw并发运行

    睿思芯科基于自研“灵羽“系列服务器CPU实测单板承载多实例OpenClaw并发运行。基于标准Linux+Docker环境并行响应不同业务需求,为企业未来的大规模Al Agent托管打开了更优单位算力成本的新路
    的头像 发表于 02-11 09:37 791次阅读
    在睿思芯科灵羽RISC-V服务器CPU实现多实例OpenClaw<b class='flag-5'>并发运行</b>

    一文说透了如何实现单片机的多任务并发

    任务并发。 一、任务调度 任务调度是多任务并发中一个非常重要的概念。它指的是如何在
    发表于 01-06 06:46

    解析Linux的进程、线程和协程

    允许在单个线程内实现多个协程的并发执行。协程在执行过程中可以主动挂起和恢复,这使得编写高效的异步代码变得更加容易。协程通常用于处理I/O密集型任务,能够提高程序的响应性能。 协程的特点包括: (1
    发表于 12-22 11:00

    高性能网络存储设计:NVMe-oF IP的实现探讨

    Initiator 并发访问支持(Multi-Initiator Sharing) 系统从协议栈到调度机制均支持多个 Initiator(多个客户端)同时访问单个Target: •每
    发表于 12-19 18:45

    并行智能体:洞察复杂系统的 14 种并发设计模式

    在AI智能体的世界中,速度、质量和可靠性不仅仅是特性,它们是必备条件。一个单一、顺序执行的智能体可能速度慢、容易出错,并且解决问题的能力有限。解决方案是采用并行思维:设计一个系统,让多个智能体、流程
    的头像 发表于 12-02 15:07 834次阅读
    <b class='flag-5'>并行</b>智能体:洞察复杂<b class='flag-5'>系统</b>的 14 种<b class='flag-5'>并发</b>设计模式

    Linux多线程对比单线程的优势

    在Linux系统中,线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运行单位。一个进程可以拥有多个线程,这些线
    发表于 12-01 06:11

    【HZ-T536开发板免费体验】—— linux创建线程

    的执行任务成为单线程。多线程是程序中包含多个执行流,在一个程序中可以同时运行多个不同的线程来执行不同的任务。 多线程提高了CPU的使用卤率。
    发表于 09-01 21:31

    Task任务:LuatOS实现“任务并发”的核心引擎

    Task任务通过其强大的并发处理能力,使LuatOS能够在单线程环境中模拟多线程执行,通过协程的挂起与恢复机制,实现任务级的并行操作,显著提升系统
    的头像 发表于 08-28 13:49 693次阅读
    Task<b class='flag-5'>任务</b>:LuatOS实现“<b class='flag-5'>任务</b>级<b class='flag-5'>并发</b>”的核心引擎

    第三届大会回顾第3期 | FFRT并发框架在OpenHarmony中的设计与实践

    ,特别是在多核处理器上,可以显著提高程序的运行速度和整体性能,从而改善用户体验。OpenHarmony的FFRT并发编程模型为开发者提供了构建异步并发任务的能力,以更高效地开发和管理
    的头像 发表于 06-21 16:53 1565次阅读
    第三届大会回顾第3期 | FFRT<b class='flag-5'>并发</b>框架在OpenHarmony中的设计与实践

    同步任务开发指导

    同步任务是指在多个线程之间协调执行的任务,其目的是确保多个任务按照一定的顺序和规则执行,例如使用锁来防止数据竞争。 同步
    发表于 06-19 07:57

    I/O密集型任务开发指导

    能力,而在于I/O操作的速度和效率。这种任务通常需要频繁地进行磁盘读写、网络通信等操作。此处以频繁读写系统文件来模拟I/O密集型并发任务的处理。 定义
    发表于 06-19 07:19

    鸿蒙5开发宝藏案例分享---应用并发设计

    1:耗时任务并发——图片解码加速 痛点 :主线程解码4K图片导致界面卡死 // 步骤1:定义并发函数 @Concurrent function decodeImage(imageData
    发表于 06-12 16:19