【iOS笔记】【12】多线程
本文最后更新于:2021年12月22日 中午
【主页】系列文章目录
【iOS笔记】系列目录
一 常见多线程实现
(一)pthread
(1)特点
- 1)一套通用的多线程API
- 2)适用于Unix/Linux/Windows等系统
- 3)跨平台可移植
- 4)使用难度大
(2)使用语言
C语言(3)使用频率
几乎不用(4)线程生命周期
由程序员进行管理(5)概念、属性与方法
略(二)NSThread
(1)特点
- 1)使用更加面向对象
- 2)简单易用,可直接操作线程对象
(2)使用语言
OC(3)使用频率
偶尔使用(4)线程生命周期
由程序员进行管理(5)概念、属性与方法
1)创建线程:
1
2
3
4
5
6
7
8
9
10
11
12
13// 实例化线程需要手动启动才能运行,手动调用[newThread start];
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//或
NSThread *newThread= [[NSThread alloc] init];
//或
NSThread *newThread= [[NSThread alloc] initWithBlock:^{
NSLog(@"initWithBlock");
}];
//启动
[newThread start];
// 通过构造器开辟子线程,不需要手动启动
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];2)常用属性
1 |
|
3)常用方法
1 |
|
(三)GCD
(1)特点
- 1)旨在替代NSThread等线程技术
- 2)充分利用设备的多核(自动)
(2)使用语言
C语言(3)使用频率
经常使用(4)线程生命周期
自动管理(5)概念、属性与方法
1)栅栏方法
多读单写1
2void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); - 1.读写互斥
- 2.写写互斥
- 3.读读并发
1 |
|
解析
- 首先我们要维系一个GCD 队列,最好不用全局队列,毕竟大家都知道全局队列遇到栅栏函数是有坑点的,这里就不分析了!
- 因为考虑性能 死锁 堵塞的因素不考虑串行队列,用的是自定义的并发队列!
_concurrent_queue = dispatch_queue_create("com.by.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
- 首先我们来看看读操作:
by_setObject:forKey:
我们考虑到多线程影响是不能用异步函数的!说明:- 线程2 获取:
name
线程3 获取age
- 如果因为异步并发,导致混乱 本来读的是
name
结果读到了age
- 我们允许多个任务同时进去! 但是读操作需要同步返回,所以我们选择:
同步函数
(读读并发)
- 线程2 获取:
- 我们再来看看写操作,在写操作的时候对key进行了copy, 关于此处的解释,插入一段来自参考文献的引用:
函数调用者可以自由传递一个
NSMutableString
的key
,并且能够在函数返回后修改它。因此我们必须对传入的字符串使用copy
操作以确保函数能够正确地工作。如果传入的字符串不是可变的(也就是正常的NSString
类型),调用copy
基本上是个空操作。
- 这里我们选择
dispatch_barrier_async
, 为什么是栅栏函数而不是异步函数或者同步函数,下面分析:- 栅栏函数任务:之前所有的任务执行完毕,并且在它后面的任务开始之前,期间不会有其他的任务执行,这样比较好的促使 写操作一个接一个写 (写写互斥),不会乱!
- 为什么不是异步函数?应该很容易分析,毕竟会产生混乱!
- 为什么不用同步函数?如果读写都操作了,那么用同步函数,就有可能存在:我写需要等待读操作回来才能执行,显然这里是不合理!
2)信号量
1 |
|
3)延时执行方法
1 |
|
4)一次性代码(只执行一次)
1 |
|
单例
1 |
|
5)快速迭代方法
1 |
|
6)队列组
dispatch_group
1 |
|
(四)NSOperation
(1)特点:
- 1)基于GCD(底层是GCD)
- 2)比GCD多了一些更简单实用的功能
- 3)使用更加面向对象
(2)使用语言
OC(3)使用频率
经常使用(4)线程生命周期
自动管理(5)概念、属性与方法
1).创建
a.使用子类 NSInvocationOperationb.使用子类 NSBlockOperation1
2
3
4
5// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.调用 start 方法开始执行操作
[op start];c.自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作1
2
3
4
5
6
7
8
9
10// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 2.调用 start 方法开始执行操作
[op start];1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22@implementation CustomNSOperation
- (void)main {
if (!self.isCancelled) {
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"current thread: %@", [NSThread currentThread]);
}
}
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 1.创建 CustomNSOperation 对象
CustomNSOperation *op = [[CustomNSOperation alloc] init];
// 2.调用 start 方法开始执行操作,在主线程调用,将不会开辟新线程
[op start];
}二 多线程的原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
思考:如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)
三 多线程的优点
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
四 多线程的缺点
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享
五 你更倾向于哪一种?
倾向于GCD:
GCD 技术是一个轻量的,底层实现隐藏的神奇技术,我们能够通过GCD和block轻松实现多线程编程,有时候,GCD相比其他系统提供的多线程方法更加有效,当然,有时候GCD不是最佳选择,另一个多线程编程的技术 NSOprationQueue 让我们能够将后台线程以队列方式依序执行,并提供更多操作的入口,这和 GCD 的实现有些类似。
这种类似不是一个巧合,在早期,MacOX 与 iOS 的程序都普遍采用Operation Queue来进行编写后台线程代码,而之后出现的GCD技术大体是依照前者的原则来实现的,而随着GCD的普及,在iOS 4 与 MacOS X 10.6以后,Operation Queue的底层实现都是用GCD来实现的。
六 GCD与NSOprationQueue的区别
(1)区别
- 1.GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
- 2.在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
- 3.NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
- 4.我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样能比GCD更加有效地掌控我们执行的后台任务;
- 5.在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
- 6.我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
总的来说,Operation queue 提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。
(二)更倾向于哪个
更倾向于NSOperation
NSOperation相对于GCD:
- 1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
- 2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
- 3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
- 4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
(三)使用NSOperation的情况
各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。
(四)使用GCD的情况
一般的需求很简单的多线程操作,用GCD都可以了,简单高效。
从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。
当需求简单,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。
七 常见提问
- 1.你理解的多线程?
- 2.可能会追问,每种多线程基于什么语言?
- 3.生命周期是如何管理?
- 4.你更倾向于哪种?追问至现在常用的两种你的看法是?
参考链接:
联系方式
邮箱: xiebangyao_1994@163.com
相关账号:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!