这是我参与「第四届青训营」笔记创作活动的的第十二天。本篇文章旨在介绍多线程的基本概念、实现方案等等。
一、多线程的基础概念
1.进程与线程
进程
程序运行的一个实例,是资源分配的最小单位,独立地运行在其专用受保护的内存空间内。一个进程可以包含多个线程。
线程
线程是操作系统实施调度的最小单位。每个线程之间共享进程的内存空间(代码段、数据段、堆等)及一些进程级的资源(如打开文件等)。每个线程有寄存器、栈、线程局部存储(TLS)等私有数据。
2.串行、并行、并发
串行
At some point, lots of people will order coffee and food. That needs more time to get prepared. Suddenly the bell rings that 5x food is ready and 4x coffees. The waiter will need to serve the tables one by one even if all the products are prepared. This is the Serial Queue.
“在某些时候,很多人会点咖啡和食物。这需要更多的时间来准备。突然铃声响起,5 份食物准备好了,4 份咖啡准备好了。即使所有的产品都准备好了,服务员也需要一一上桌。这是串行队列。“
引用:betterprogramming.pub/the-complet…
从这个例子中可以知道,串行队列需要有序的挨个执行任务,前一个任务执行完之后才能执行下一个任务。 在这个例子中以每一份食物、每一份咖啡为一个任务,服务员不能一次性把所有的食物和咖啡同时端上桌,而是需要把它们一个一个端上桌,也就是说一个服务员一次只能完成一个任务,一个任务结束后再开始进行下一个任务。
并行
Now imagine there are two or three waiters. They can serve the tables much faster at the same time. This is Parallelism.
“现在想象有两三个服务员。他们可以同时更快地擦桌子。这就是并行。“
引用:betterprogramming.pub/the-complet…
从这个例子中可以知道,并行就是说多个任务在同一时刻被执行。在这个例子中以擦桌子为任务,多个服务员可以擦多个桌子。如果擦一个桌子为一个任务,那么多个服务员可以同时执行多个任务。
并发
多个任务在同一时间段内需要被执行,侧重点是这个现象的“发生”。多线程编程又称为并发编程,让多个 CPU 并发处理多个线程的指令,从而提高程序的执行效率。
3.多线程的优缺点
优点:不用跨进程边界;程序逻辑和控制方式简单能适当地提高程序的执行效率;提高资源利用率。
缺点:创建线程、线程调度都有开销成本,如果开启大量线程,会降低程序的性能。需要谨慎处理线程安全问题,一个线程的崩溃可能影响到整个程序的稳定性,增加了程序设计的复杂度。
二、多线程实现方案与GCD
GCD(Grand Central Dispatch)
GCD is Apple’s low-level threading interface for supporting concurrent code execution on multicore hardware. In a simple manner, GCD enables your phone to download a video in the background while keeping the user interface responsive.
“GCD 是 Apple 的低级线程接口,用于支持多核硬件上的并发代码执行。以一种简单的方式,GCD 使您的手机能够在后台下载视频,同时保持用户界面响应。“
引用:betterprogramming.pub/the-complet…
GCD 抽象出了任务、队列等概念,将并发编程的范式变为了定义想执行的任务并追加到适当的派发队列。
GCD接口中的dispatch_group
Often we need to start multiple async processes, but we need just one event when all are finished. This can be achieved by DispatchGroup.
“通常我们需要启动多个异步进程,但是当所有进程都完成后我们只需要一个事件。这可以通过 DispatchGroup 来实现。“
- Step 1. Create DispatchGroup
- Step 2. Then for that group need to call group.enter() event for every task started
- Step 3. For every group.enter() needs to be called also the group.leave() when the task is finished.
- Step 4. When all the enter-leave pairs are finished then group.notify is called. If you notice it is done on the background thread. You can configure per your need.
"
- 创建DispatchGroup。
- 然后对于该组需要为group.enter()启动的每个任务调用事件。
- 对于每个group.enter()需要group.leave()在任务完成时调用。
- 当所有进入离开对完成时,然后group.notify调用。如果您注意到它是在后台线程上完成的。您可以根据需要进行配置。 "
引用:betterprogramming.pub/the-complet…
当队列中所有或多个任务执行完以后要再执行某任务,可以通过dispatch_group、dispatch_group_notify 实现。除此之外,使用dispatch_group_wait可以设置等待group 执行的时间上限,当 group 中全部任务执行完或者满足 timeout 条件 dispatch_group_wait 才返回,可通过返回值区分两种返回类型。还有wait(timeout:)选项。它将等待一段时间以完成任务,但超时后,它将继续。
三、线程安全
锁&死锁
锁:互斥量(Mutex) 是最简单的一种锁,在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用。由哪个线程获取就由哪个线程释放,常用于临界区的互斥访问。
死锁:有一种情况是两个任务可以互相等待完成。这称为死锁。该任务将永远不会执行,并将阻止应用程序。
产生死锁的四个必要条件:
- 互斥条件
- 占有且等待条件
- 不可抢占条件
- 循环等待条件
线程安全中的dispatch_barrier_async
dispatch_barrier_async:提交一个用于异步执行的屏障块并立即返回。
Dispatch Barriers is resolving the problem with a read/write lock. This makes sure that only this DispatchWorkItem will be executed.
“This makes thread-unsafe objects thread-safe.” — Apple Docs
“Dispatch Barriers 正在解决读/写锁的问题。这确保只有这个 DispatchWorkItem 将被执行。
“这使得线程不安全的对象成为线程安全的。” “
dispatch_barrier_async 函数会等队列中的全部任务执行结束后,再将指定的任务 X 追加到队列,之后提交的任务也需要等待 X 执行结束,仿佛 dispatch_barrier_async给队列添加了一道“栅栏”。
总结
在程序包含复杂的计算任务时使用多线程能够节约更多时间。就像前文提到的例子一样,聘用多个服务员能够提升效率、节省时间。这也是多线程为什么重要的原因之一。
Reference
《The Complete Guide to Concurrency and Multithreading in iOS》betterprogramming.pub/the-complet…
《Objective-C 多线程编程与 GCD》juejin.cn/post/712344…
《一文讲解进程、线程、多进程、多线程的优缺点》 cloud.tencent.com/developer/a…
《dispatch_barrier_async》developer.apple.com/documentati…