CPU不同调度:一个进程可以开启多条线程,每条线程可以并行执行不同的任务。
任务执行完毕依赖于线程池:不一定按照固定的顺序
- 进程:系统中正在运行的一个应用程序
- 线程:一个进程要想执行任务,必须得有线程,线程是进程的执行路径
-
多线程原理:同一时间,CPU只能从事处理1条线程,只有1条线程在工作。多线程并发执行,其实就是CPU快速地在多条线程之间切换(
CPU调度线程的时间足够快,就造成了多线程并发执行的假象
) - 多线程的优缺点:
- 优点:提高程序的执行效率,提高资源利用率
- 缺点:线程是有开销的,创建线程大约需要90毫秒的创建时间
- 缺点:如果开启大量线程,会降低程序的性能。
- 缺点:线程越多,CPU调度开销越大;
- 应用:
一个iOS程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”。线程的主要作用:显示/刷新UI;处理UI事件(比如点击、滚动、拖拽事件等。)
使用注意:别将比较耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。
比如说:一个操作需要耗时10秒,但是用户在第5秒时点击了按钮就会很卡 5秒后才能执行。
解决:****将耗时操作放在子线程,当用户第五秒点击按钮那一刻就有反应,能同时处理耗时操作和UI控件的事件。
一、NSThread
创建和启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 线程一启动,就会在线程thread中执行self的run方法
其他创建线程的方式
//①创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
//②隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];//所有继承NSObject的类都可以调用
上述2种创建线程方式的优缺点 优点:简单快捷 缺点:无法对线程进行更详细的设置
控制线程的状态:
NSThread 线程状态:阻塞,就绪,死亡
//①启动线程
- (void)start; //将线程加入到线程池里面-》CPU调度线程 运行状态由CPU控制
//②进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// ③进入阻塞状态 时间到了从阻塞到就绪状态
强制停止线程
+ (void)exit;//④进入死亡状态 从线程池中拿出来,销毁 注意:一旦线程停止(死亡)了,就不能再次开启任务
线程通信
在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
//①开启子线程:
NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(downImage) object:nil];[t1 start];
//②回到主线程:
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];//YES 相当于给Timer加了一个Runloop
二、GCD
全称是Grand Central Dispatch
,可译为“牛逼的中央调度器
”,纯C语言,提供了非常多强大的函数。
苹果公司为多核的并行运算提出的解决方案、自动管理线程的生命周期、只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
核心概念
将任务添加到队列,队列按照程序员指定的方式调度任务,并发和串行主要影响:任务的执行方式。
将任务添加到队列,并指定执行任务的函数
定制任务 -> 将任务添加到队列 -> (GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出)
- 同步:第一个任务没有结束,就不会执行下一个任务
- 异步:不用等待任务执行完毕,就会执行下一个任务
- 并发:允许多个任务同时执行
- 串行:一个任务执行完毕后,再执行下一个任务
同步执行(sync这一句不执行,就不会执行下一句)
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
void (^tasks)(void) = ^{
NSLog(@"%@",[NSThread currentThread]);
};
dispatch_sync(q, tasks);
异步执行(如果任务没有完成,可以不等待,异步执行下一个任务[具备开启线程的能力])
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
void (^tasks)(void) = ^{
NSLog(@"%@",[NSThread currentThread]);
};
dispatch_async(q, tasks);
线程间通信
// 异步执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 耗时操作
NSLog(@"%@",[NSThread currentThread]);
// 更新UI
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
NSLog(@"更新UI%@",[NSThread currentThread]);
});
});
队列
-串行队列:一个接着一个地执行任务
-并发队列:可以同时调度多个任务
-任务执行函数(任务都需要在线程中执行)
并发队列(Concurrent Dispatch Queue
)
- 自动开启多个线程同时执行任务
- 并发功能只有在异步(dispatch_async)函数下才有效
- dispatch_get_global_queue 获取全局的并发队列
//GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
//优先级
/*
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
*/
串行队列(Serial Dispatch Queue
)
让任务一个接着一个地执行
GCD中获得串行有2种途径
//1.使用dispatch_queue_create函数创建串行队列
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
//2.使用主队列
dispatch_queue_t queue = dispatch_get_main_queue();
GCD调度组 dispatch_group_t
//1.队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.调度组
dispatch_group_t g = dispatch_group_create();
//3.添加任务,将队列调度任务。任务完成的时候,能够提醒
dispatch_group_async(g, q, ^{
NSLog(@"download A %@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B %@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download C %@",[NSThread currentThread]);
});
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
//更新UI,通知用户
NSLog(@"OK %@",[NSThread currentThread]);//1
});
NSLog(@"Come Here ");
dispatch_barrier_async
这个函数要等在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。(这个queue不能是全局的并发队列)
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
三、NSOperation
将“操作”添加到“队列”。
NSOPeration抽象类 对GCD面向对象的封装,
特点:不能直接使用 目的:定义子类共有的属性和方法.
子类:NSInvocationOperation、NSBlockOperation
-最大并发数
-队列的暂停、继续
-取消所有操作
-提供线程依赖关系(线程A 的执行 必须依赖线程B)
回归主线程:
[self.opQueue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
//主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"Main Thread %@",[NSThread currentThread]);
}];
}];
提供依赖关系
- (void)dependency {
/**
* 下载、解压、通知用户
*/
//1.下载
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"下载--%@ ",[NSThread currentThread]);
}];
//2.解压
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"解压--%@ ",[NSThread currentThread]);
}];
//3.通知用户
NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户--%@ ",[NSThread currentThread]);
}];
NSLog(@"Come here");
//NSOperation 提供依赖关系
[op2 addDependency:op1];
[op3 addDependency:op2];
//[op1 addDependency:op3];//循环依赖 此时队列不执行工作,也不会崩溃
//添加队列
[self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];// YES在所有任务都完成后才会走Come here.
//在主线程上通知用户
[[NSOperationQueue mainQueue] addOperation:op3];
NSLog(@"Come here");
}
四、信号量
信号量其实就是一个整数值并具有一个初始化的值,有关信号量有两个操作:
信号通知、信号等待
注意
当信号量被信号通知时,其计数会被增加。
当线程在信号量等待时,线程会阻塞,计数会减少
三种重要的函数
dispatch_semaphore_create
创建一个信号量,具有整形的数值,即为信号的总量。
dispatch_semaphore_signal
返回值为long类型,当返回值为0时,表示当前并没有线程等待其处理的信号量,其处理的信号总量增加1。
当返回值不为0时,表示其当前有一个或者多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级的时候,唤醒优先级最高的线程,否则随机唤醒)。
补充:
1.GCD | NSOperation 区别:
//NSOperation 和 GCD 对比:
//GCD 在 iOS 4.0 推出,针对多核处理器优化的并发技术,是C语言的
-将任务(block)添加到队列 (串行、并行、主队列、全局队列),并且要指定任务的同步、异步;
-线程间的通讯 dispatch_get_main_queue()
-提供了一些 NSOperation 不具备的功能:
一次执行、 -延迟执行 、-调度组(NSoperation 也可以做到,但有点儿麻烦)
// NSOperation 在 iOS 2.0; 苹果推出了GCD后,对NSOperation 进行了重写
-将操作[异步执行的任务添加到并发队列],就会立刻异步执行
-提供了一些 GCD实现起来比较麻烦的功能 【下载】
-最大并发数
-队列的暂停、继续
-取消所有线程
-线程依赖(线程A 的执行 必须依赖线程B)(GCD 同步实现!)
- NSThread -> pthread 队列:GCD 并发队列
- NSOperation -> GCD 操作:异步执行任务
2.多线程隐患:多个线程访问同一个资源,容易引发数据错乱和数据安全问题
==互斥锁==
@synchronized(锁对象self) { // 需要锁定的代码 }
注意:锁定1份代码只用1把锁,用多把锁是无效的
//当Thread A 执行完+1操作后,就把线程锁住,ThreadB无法访问,等到Thread A 用完之后解锁,此时ThreadB才能访问。
能有效防止因抢夺资源造成的数据安全问题 缺点:需要消耗大量CPU资源
互斥&自旋锁🔐
保证共享数据操作的完整性,保证在任一时刻,只能有一个线程访问对象
1.互斥锁
- 互斥锁:如果发现其他线程正在执行锁定代码,线程就会进入休眠状态,等待线程完成,就唤醒
- 加了互斥做的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。
2.自旋锁
- 自旋锁:如果发现其他线程正在执行锁定代码,线程就会死循环模式,等待线程完成,就执行锁定代码
- 自旋锁的效率高于互斥锁。
- 加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。
3.原子和非原子性
原子性:线程安全,消耗大量资源(多个线程操作数据,保证线程正确)
非原子性:非线程安全,适合内存小的移动设备(保证同一时间只有一个线程能够写入)
4.线程间通信
回到主线程
①[self performSelectorOnMainThread]
②[[NSOperationQueue mainQueue] addOperationWithBlock:];
③GCD
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新UI%@",[NSThread currentThread]);
});
5.GCD如何实现线程安全?
分发队列本身是线程安全的。你可以在任意线程中将任务提交到分发队列中,无需给队列加锁或使用同步机制
6.GCD 队列组如何取消其中一个
GCD原生并不支持取消已经加入queue的block
dispatch_block_cancel(^{});
如何实现
正常情况下,GCD的任务一旦开始执行时无法停止的,但是我们可以加一个外部变量,用于标记block是否需要取消。然后block中通过及时检测这个外部变量的状态,当发现需要取消时,停止block中的后续操作。就能达到及时取消block的目的。
7.利用NSOperation与NSOperationQueue处理多线程时,有3个NSOperation分别为A,B,C,要求A,B执行完之后,才执行C,如何做?
A.添加依赖
//创建列队
NSOperationQueue *queue = [NSOperationQueue alloc]init];
//创建3个操作
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation1....");
}];
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation2....");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation3....");
}];
//======添加依赖=======
//只有当a操作执行完毕后,才会执行c操作
[c addDependency:a];
//只有当b操作执行完毕后,才会执行c操作
[c addDependency:b];
[queue addOperation:a];
[queue addOperation:b];
[queue addOperation:c];
B.设置优先级
- (NSOperationQueuePriority)queuePriority;
8.NSOperatinQueue和CGD的区别,什么情况下用NSOperationQueue,什么情况下用GCD
比较
1.GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
2.GCD只支持FIFO的列队(先进先出),NSOperationQueue可以很方便地调整执行顺序(设置优先级 ),设置最大并发数量
3.NSOperatinQueue可以设置Operation间依赖关系,而GCD需要些很多代码才能实现
4.NSOperationQueue支持KVO,可以监听operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld);系统内部已经做好的KVO
5.GCD的执行速度比NSOperationQueue快
什么时候,用哪个?
1.任务之间有依赖/或者要监听任务的执行情况:NSOperatinQueue(任务需要时刻监听;任务严格需要按顺序执行)
2.任务之间不太相互依赖就用:GCD
9.如何用GCD同步若干个异步调用,请用代码说明?
//获取并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//获取组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^ {
NSLog(@ "线程:%@--加载图片1", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^ {
NSLog(@ "线程:%@--加载图片2", [NSThread currentThread]);;
});
dispatch_group_async(group, queue, ^ {
NSLog(@ "线程:%@--加载图片3", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^ {
NSLog(@ "线程:%@--合并图片", [NSThread currentThread]);
});