这里是Swift并发编程系列
第一篇(共四篇)。包含以下内容:
DispatchWorkItem
假设您在主线程上并且需要来自服务器的数据。您从服务器请求数据并等待,直到您从服务器获得响应。在此期间,您的主线程不会执行任何与 UI 相关的工作,这会使您的应用程序无响应。
假设服务器在此期间需要 10 秒才能给出响应,如果用户点击按钮,系统将不会响应它,这对用户来说非常糟糕。
如果你可以在同一时间(或大约在同一时间)运行的这两个任务,一个线程专门处理用户界面相关的工作,其他的线程处理耗时任务。这样一来,上面的情况就不会发生了。
并发意味值应用程序可以使用分割时间
的方式同时处理多个任务。如果一台计算机只有一个CPU
,那么它无法在一个精确的时间点
上同时运行多个任务,但是可以通过上下文切换
的方式在一段时间(很短,比如1s)内执行多个任务。
上下文切换
是指存储线程的状态,并在将来恢复这个状态继续执行。这允许多个进程共享一个CPU
资源,同时这也是多任务操作系统的基本功能。
并行指多个任务同时发生,并没有上下文切换。
对于我们讨论的情况,如果并行地执行网络调用,那么将有两个线程在两个不同的内核上执行主线程和后台指令,与前一个相比,速度非常快,但需要额外的物理要求-需要CPU
有多核。
如下图所示,在并行的情况下,两条线程真的在同时执行;而并发,在一个线程执行时,另一个在休眠。
CPU
上,如果你创建了10条线程,那么它只能使用并发
/时间片分割
/上下文切换
的方式执行它们10核CPU
上,如果你创建了10条线程,那么它们可能以下面的方式执行:并发
执行并行
执行并发
执行,另外一部分并行
执行CPU
上,如果你创建了1000条线程,那么CPU
只会忙着上下文切换
,而不会执行实质性的任务。可见,创建合理的线程数量也是一个不小的挑战。GCD 在幕后管理共享线程池并在该池中添加最佳线程数。使用 GCD,您将代码块或工作项添加到队列中,GCD 决定在哪个线程上执行它们。GCD 根据系统物理条件或当前负载并发或并行执行此任务。
注意:如果你给 GCD 分配两个任务,你不确定它是并发还是并行运行。
从现在开始,我们将使用术语并发
代表并发/并行。
您所要做的就是定义要并发执行的任务并将它们添加到适当的调度队列中。GCD 负责创建所需的线程并安排您的任务在这些线程上运行,这非常酷😃
调度队列是一种基于 C 的组件,用于执行自定义任务。调度队列总是按照任务添加到队列的顺序出列和启动任务。调度队列是线程安全的,这意味着您可以同时从多个线程访问它们。注意,队列不是线程!
如果您想通过 GCD 执行并发任务,请将它们添加到适当的调度队列中。GCD 将基于队列的配置,挑选并执行任务。
如下图所示,我们创建了两个全局并发队列,您可以看到由于全局队列是整个系统共享的并发队列,因此它始终返回相同的队列;而自定义并发队列是私有的,每次创建时都会返回新队列。
有四个不同优先级的全局并发队列,但在设置全局并发队列时,不直接指定优先级。
相反,您指定服务质量 (QoS),其中包括用户交互、用户启动、实用程序和后台,其中用户交互具有最高优先级,而后台具有最低优先级。下面是QoS
的使用建议。
.userInteractive
标志任务需要被立即执行以便提供更出色的用户体验。通常用来做UI
更新,事件处理等低延时的任务。该类型的任务不应过多。.userInitiated
标志任务被用户通过UI界面创建,但是可以被异步执行。通常用在用户一个操作后需要等待,结果返回后继续之前的操作。.default
默认值。用于一般性异步任务。.utility
标志任务需要较长时间,通常会关联一个进度。比如:I/O
、网络请求等。.background
标志任务的执行用户不太会关心。比如:预加载数据。与全局队列相比,您可以使用自定义队列执行以下任务:
queue.suspend() queue.resume()
栅栏任务
: queue.async(flags: .barrier) { ... }
我们已经学习了如何在队列上串行或并发地执行任务。使用 GCD,您还可以同步或异步的调度队列。
一般来说,同步函数(sync
)在任务完成后将控制权返回给调用者。而异步函数(async
)会在函数调用后,立即将将控制权返回给调用者,它不会等等任务完成。
如上图,您在并发全局队列上执行耗时的任务,但主线程仍然很忙,因为您在主线程 main
上同步地在分派任务,它会一直等到任务执行完成。
如上图:我们异步的分派任务,它立即返回到主线程,主线程将首先打印,队列上的列任务将并发执行。
在并发计算中,死锁是一个组的每个成员都在等待另一个成员(包括它自己)采取行动的状态
在上图中,queue
是一个串行队列,通过async
派发的任务A,通过sync
派发了任务B。此时A会等待B完成后继续向下,而B在A没有完成之前是不会开始。这就造成了死锁。
同样,上图的主队列任务A(viewDidLoad方法)使用sync
派发任务B,也会造成相互等待从而死锁。
DispatchWorkItem
是一项任务的包装器,可以多次使用,也可以取消。
let queue = DispatchQueue(label: "com.swiftpal.dispatch.workItem")
// Create a work item
let workItem = DispatchWorkItem() {
print("Stored Task")
}
// Task 1
queue.async(execute: workItem)
// Task 2
queue.asyncAfter(deadline: DispatchTime.now() + 1, execute: workItem)
// Work Item Cancel
workItem.cancel()
// Task 3
queue.async(execute: workItem)
if item.isCancelled {
print("Task was cancelled")
}
这里我们创建了一个串行队列,又创建了一个DispatchWorkItem
,它只包含一句代码。
接下来,我们在取消任务之前派发了该任务两次,之后再次派发该任务。但是对于输出,我们只会看到一次Stored task
。
原文地址:https://ali-akhtar.medium.com/concurrency-in-swift-grand-central-dispatch-part-1-945ff05e8863
作者:泥瓦罐
https://juejin.cn/post/7049357208710021156
-End-
最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!
面试题
】即可获取文章引用微信公众号"程序员大咖",如有侵权,请联系管理员删除!