什么是多线程?
说白了就是CPU快速的在多条线程之间调度(即切换),且多条线程可以并发执行(即同时执行)
提到多线程,不得不提一下进程
那什么是进程了?
可以理解为在系统中正在运行的程序,每个进程之间是独立的,而且每个进程均运行在其专有且受保护的内存空间.
什么线程?
独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。
那进程和线程有什么关系了?
其实一个进程想执行任务,那么他就需要线程,因为进程中的所有任务都是在线程中执行的,故每个进程至少拥有一条线程(即我们的主线程)
在说下主线程吧?
又称UI线程,其实就是一个iOS程序运行后,默认开启的第一条线程,我们称为主线程,作用主要是
1.处理UI事件(比如点击事件,滚动事件,拖拽事件等等),
2.刷新UI
那再来说说多线程的意义吧?(个人理解)
举个例子,就像我们听歌一样,如果我在同时听歌的时候又想下载歌曲,如果只有一条线程的话,我们知道一条线程同一时间只能执行一个任务,为了满足这个需求,多线程的意义在此,所谓的多线程主要是就是CPU快速在多条线程之间调度,(并发执行)
那么多线程有哪些实现方案了?
1.pthread (C语言 是一套通用的多线程API 线程的生命周期是需要程序员手动管理的)
2.NSThread (OC 使用更加面向对象 可以直接操作线程对象 但是线程的生命周期也是需要程序员手动管理的)
- 优点:轻量级,简单易用,可以直接操作线程对象
- 缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
3.GCD (C语言 充分利用设备的多核 生命周期自动管理)
- 优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
- 缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。
4.NSOperation (OC 基于GCD的 使用更加面向对象 线程生命周期自动管理)
- 优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
- 缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
多线程的优缺点?
有利必有弊嘛没有十全十美的,当然多线程也不例外
优点:
1.能够适当的提高程序的执行效率
2.适当的提高资源利用率(CPU,内存利用率)
缺点:
1.如果大量的开启线程,会降低程序的性能
2.线程越多,那么CPU的工作量越大
3.程序的设计就更加复杂(例如线程之间的通信,多线程的数据共享)(推荐3到5条)
NSThread(个人用的比较少)
一个NSThread对象就代表一条线程,生命周期需要手动管理
线程状态有
1.新建New (进入就绪状态->运行状态.当线程任务执行完毕,自动进入死亡状态-> - (void)start)
2.就绪Runnable
3.强制停止线程
4.运行状态 Running
5.阻塞 Blocked
6.死亡 (一旦线程停止(死亡)了,就不能再次开启任务了)
互斥锁
1.使用格式:@synchronized(锁对象) // 需要锁定的代码
2.使用优缺点:
a.优点:能防止因为多线程抢夺资源造成的数据安全问题.
b.缺点:需要消耗大量的CPU资源
3.使用原理:就是使用了线程同步技术,多条线程在同一条上执行(按顺序的执行任务)
NSOperation
首先NSOperation是一个抽象类,并不具备封装操作的能力,必须使用NSOperation子类的方式有3种:
NSInvocationOperation
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
实现步骤
1.先将要执行的操作封装到一个NSOperation对象中
2.然后将NSOpration对象添加到NSOprationQueue(队列)中
3.系统会自动将NSOprationQueue中的NSOpration取出来
4.将取出来的NSOpration封装的操作放到一条新线程中执行
那NSOprationQueue又是什么了?
我们称之为队列,只要是自己创建的队列,就会在子线程中执行,而且默认是并发的,且队列可以取消,暂停,恢复,但是这些操作只会对后面未执行的任务进行操作,不会影响当前正在执行的,且取消不可恢复
自己创建: alloc/init --> 默认是并发 --> 也可以让它串行
主队列 : mainQueue
NSOperationQueue的作用
NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block
什么是并发数
同时执行的任务数
比如,同时开3个线程执行3个任务,并发数就是3
最大并发数的相关方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
队列的取消、暂停、恢复
取消队列的所有操作
- (void)cancelAllOperations;
提示:也可以调用NSOperation的- (void)cancel方法取消单个操作
暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended;
依赖
NSOperation之间可以设置依赖来保证执行顺序
设置依赖来保证执行顺序,可以设置不同队列中的opration创建的依赖关系
[operationB addDependency:operationA]; // 操作B依赖于操作A
使用,使用两个图片拼接这种典型
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self setUp];
}
- (void)setUp
{
__block UIImage *image1=nil;
__block UIImage *image2=nil;
//创建一个异步队列
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
//开启一个子线程下载图片
NSOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
NSURL *url=[NSURL URLWithString:@"http://s.wasu.cn/data/images/201604/13/570e0b67c15fb.jpg"];
NSData *date=[NSData dataWithContentsOfURL:url];
image1=[UIImage imageWithData:date];
NSLog(@"---op1----%@",[NSThread currentThread]);
}];
//下载另一个图片
NSOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
NSURL *url=[NSURL URLWithString:@"http://i1.hdslb.com/bfs/archive/5e91addadc849572575b592814846977ab5559f1.jpg"];
NSData *date=[NSData dataWithContentsOfURL:url];
image2=[UIImage imageWithData:date];
NSLog(@"---op2----%@",[NSThread currentThread]);
}];
//拼接图片
NSOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
CGSize size=CGSizeMake(300, 400);
UIGraphicsBeginImageContext(size);
[image1 drawAsPatternInRect:CGRectMake(0, 0, 150,400)];
[image2 drawAsPatternInRect:CGRectMake(150, 0, 150, 400)];
UIImage * image=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
NSLog(@"进入主线程");
self.imageView.image=image;
}];
NSLog(@"---op3----%@",[NSThread currentThread]);
}];
op1.completionBlock=^{
NSLog(@"第一张图下载完毕");
};
op2.completionBlock=^{
NSLog(@"第二张图下载完毕");
};
//依赖要放在添加队列之前
[op3 addDependency:op1];
[op3 addDependency:op2];
NSArray *opreations=[NSArray arrayWithObjects:op1,op2,op3, nil];
[queue addOperations:opreations waitUntilFinished:NO];
}
@end
output:
2016-06-27 17:35:39.651 图片合成[10336:2417737] ---op2----<NSThread: 0x7fe702c4c4e0>{number = 3, name = (null)}
2016-06-27 17:35:39.651 图片合成[10336:2417736] 第二张图下载完毕
2016-06-27 17:35:40.528 图片合成[10336:2417738] ---op1----<NSThread: 0x7fe702e80fa0>{number = 4, name = (null)}
2016-06-27 17:35:40.529 图片合成[10336:2417781] 第一张图下载完毕
2016-06-27 17:35:40.552 图片合成[10336:2417666] 进入主线程
2016-06-27 17:35:40.552 图片合成[10336:2417737] ---op3----<NSThread: 0x7fe702c4c4e0>{number = 3, name = (null)}
操作的监听
可以监听一个操作的执行完毕
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
自定义NSOperation
自定义NSOperation的步骤:
重写- (void)main方法,在里面实现想执行的任务
重写- (void)main方法的注意点
自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
GCD
- GCD的2个核心:任务,队列
- 任务:即为在block中执行的代码
- 队列:用来存放任务的
- 注意事项:队列!=线程.队列中存放的任务最后都要由线程来执行.队列的原则:先进先出,后进后出(FIFO)
队列
- 队列分4种
-- 串行队列:任务一个一个的执行
-- 并发队列:任务并发执行
-- 主队列:根主线程相关的队列,主队列的任务是在主线程中执行的
-- 全局队列:一个特殊的并发队列
- 并发队列与全局队列的区别
-- 并发队列有名称,可以跟踪错误.全局队列没有
-- 在ARC中2个队列不需要考虑内存释放,但是在MRC中并发队列创建出来需要release操作
-- 一般开发过程中我们使用全局队列 - 同步和异步的区别
-- 同步:(dispatch_sync)只能子安当前线程中执行任务,不具备开启新线程的能力
-- 异步:(dispatch_async)可以在新的线程中执行任务,具备开启线程的能力 - 各个队列的执行效果
-- 同步串行队列:即在当前线程中顺序执行
-- 异步串行队列:开辟一条新线程,在该线程中顺序执行
-- 同步并发队列:不开辟新线程,在当前线程中顺序执行
-- 异步并发队列:开辟多条线程,并且线程会重用,无序执行
-- 异步主队列:不开辟新线程,顺序执行
-- 同步主队列:会造成死锁(主线程与主队列相互等待,卡住主线程) - 线程间的通讯:(经典案例)子线程进行耗时操作(例下载更新等),回主线程刷新UI
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理耗时操作的代码块...
dispatch_async(dispatch_get_main_queue(), ^{ // 通知主线程刷新
//回调或者说是通知主线程刷新,
});
});