##1、基本概念
-
什么是进程: 进程是在系统运行的一个程序,每个进程之间是独立的,每个进程均运行在其专有且受保护的内存空间内。
-
什么是线程: 一个进程想要执行任务,必须得有线程(至少一个线程),线程是进程的基本执行单位,一个进程的所有任务都必须在线程中执行。
-
线程的串行: 一个线程中任务的执行是串行的,如果要在一个线程中执行多个任务,只能一个一个的按顺序执行
##2、多线程
-
什么是多线程:
一个进程中可以开启多个线程,每个线程可以并发/并行执行不同的任务,多线程可以提交程序的执行效率,比如同时执行任务ABC。
-
多线程原理:
同一时间,CPU只能执行一个线程,只有一个线程正在执行,多线程并发执行,其实是CPU快速的在多个线程之间切换,如果CPU的切换线程的时间足够快,就会造成多线程并发执行的假象。
-
多线程的优缺点:
优点: 1.能适当的提高程序的执行效率 2.能适当的提高资源的利用率 缺点: 1.开启线程会占用一定的内存空间(主线程1M,子线程0.5M),如果开启过多的线程就会占用大量的内存空间,降低程序的性能。 2.线程越多,CPU在调度线程上的开销就越大。
##3、主线程
一个IOS程序运行以后,默认会开启一个线程,这个线程就被称为主线程或(UI线程)。主线程的主要作用是显示/刷新UI界面,处理UI事件(点击,滚动,拖拽等)。
IOS中的多线程:
iOS中有四种多线程编程的技术:
1.Pthread (基本不会使用,线程的生命周期由我们⾃己管理)
2.NSThread(每个Thread对象对应⼀一个线程)(使⽤用得⽐比较少,线程的⽣生命周期由我们⾃己管理)
3.NSOperation(面向对象的线程技术)(基于gcd来实现,经常使⽤,⽣命周期由系统管理)
4.GCD(是基于C语⾔言的框架,可以充分利用多核,是苹果推荐使⽤的多线程技术)(经常使用,生命周期由系统管理))
以上这四种编程⽅方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。但是就目前而言,iOS的开发者,需要了解三种多线程技术的基本使⽤用过程。因为很多 框架技术分别使用了不同多线程技术。
##4、NSThread
创建线程、执行下载任务,需要start手动开启执行
NSThread *thread_A = [[NSThread alloc] initWithTarget:self selector:@selector(run_A) object:nil];
thread_A.name = @"线程A";
启动任务
[thread_A start];
阻塞线程,等待几秒,是这个线程里面的所有任务一起阻塞
[NSThread sleepForTimeInterval:5];
阻塞到某个时间
[NSThread sleepUntilDate:[[NSDate date] dateByAddingTimeInterval:1000]];
方式二:指定任务直接执行不需要手动开启 start
[NSThread detachNewThreadSelector:@selector(run_A) toTarget:self withObject:nil];
取消进程
[thread_A cancel];
获取当前线程信息+ (NSThread *)currentThread;
获取主线程信息+ (NSThread *)mainThread;
ps:如果是有关UI更新的操作,在其它线程中处理完之后要回到主线程进行改变
##5、GCD
(GCD)是Apple开发的⼀个多核编程的解决⽅方法。该⽅方法在Mac OS X 10.6首次推出,并随后被引入到了iOS4.0中。GCD是⼀个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。
###5.1 任务和队列
在GCD中,加入了两个非常重要的概念:任务和队列。
任务:
即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。
任务有两种执行方式: 同步执行 和 异步执行,
-
同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!
-
如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
-
如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
队列:
- 用于存放任务。一共有两种队列, 串行队列
DISPATCH_QUEUE_SERIAL
和 并行队列DISPATCH_QUEUE_CONCURRENT
.
放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。
放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
并行队列 中的任务根据同步或异步有不同的执行方式。请看下表:
主队列 | 全局队列 | 自定义串行队列 | 自定义并行队列 | |
---|---|---|---|---|
同步 | 死锁(一直卡主) | 在主线程中顺序执行 | 在主线程中顺序执行 | 在主线程中顺序执行 |
异步 | 在主线程中顺序执行 | 在其它线程中同时执行 | 在其它线程中顺序执行 | 在其它线程中同时执行 |
###5.2 创建队列
- 主队列
这是一个特殊的串行队列。它用于刷新UI,任何需要刷新UI的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。
同步任务:dispatch_sync,会阻塞后面的任务,必需当前任务完成后才能执行下一个
主队列中不能执行同步操作,不然会死锁
异步执行:dispatch_async,不会阻塞后面的任务,任务会马上返回下一个任务不需要等待,当这个任务完成后会通知主线程。 dispatch_get_main_queue()
获取主队列,主队列中的任务都会在主线程中执行, 是一个串行队列
dispatch_async(dispatch_get_main_queue(), ^{
[self run_D];
});
- 全局队列
dispatch_get_global_queue()
全局队列,是一个并行队列,可以将队列中的任务放到不同的线程中执行
任务执行的优先级
DISPATCH_QUEUE_PRIORITY_HIGH 2 最高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 中等(默认)
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self run_A];
});
如果在并行队列中同步执行任务,那么这些任务都会在主线程中按顺序执行,也就没有并发性了。
- 自定义串行队列
自定义的串行队列中异步执行任务,队列会把任务放到一个新的线程中按顺序执行
dispatch_queue_t serialQueue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
[self run_A];
});
- 自定义的并行队列
自定义的并行队列中异步执行任务,队列会把任务放到不同的线程中执行
ispatch_queue_t concurrentlQueue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentlQueue, ^{
[self run_A];
});
将更新UI的操作放到主队列中
dispatch_async(dispatch_get_main_queue(), ^{
self.imagView_1.image = image;
});
###5.3 队列组
队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。
创建一个队列组
dispatch_group_t group = dispatch_group_create();
创建队列
dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
在group中异步执行任务,block中调用局部变量需要加__block
__block UIImage *image_1;
dispatch_group_async(group, queue, ^{
image_1 = [self downloadImage_one];
});
当group中的任务都执行完后,就会发送一个通知notify调用这个方法
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imagView_1.image = image_1;
});
###5.4 线程安全
一次执行,block中的代码只能被执行一次,是线程保护的(同时只能一个线程访问)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self run_B];
});
单例一般用这个 线程安全的单例创建
+ (instancetype)shareUser_GCD {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
user = [[self alloc] init];
});
return user;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static User *user = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
user = [super allocWithZone:zone];
});
return user;
}
延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self run_B];
});
##6、NSOperation和NSOperationQueue
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:
1.将要执行的任务封装到一个 NSOperation 对象中。 2.将此任务添加到一个 NSOperationQueue 对象中。
直接执行会在主线程中顺序执行
NSInvocationOperation *opertaion_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_A) object:nil];
启动任务
[opertaion_1 start];
至少会有一个任务在主线程中执行,其他任务会被放到其他线程中执行
NSBlockOperation *opertion = [NSBlockOperation blockOperationWithBlock:^{
[self run_A];
}];
[opertion addExecutionBlock:^{
[self run_B];
}];
[opertion addExecutionBlock:^{
[self run_C];
}];
[opertion start];
###6.1 NSInvocationOperation依赖关系
NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖
注意:
- 不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
- 可以使用 removeDependency 来解除依赖关系。
- 可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。
NSInvocationOperation *opertaion_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_A) object:nil];
NSInvocationOperation *opertaion_2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_B) object:nil];
NSInvocationOperation *opertaion_3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run_C) object:nil];
// 添加依赖,opertaion_1 ————》opertaion_2 ————》opertaion_3
// [opertaion_2 addDependency:opertaion_1];
// [opertaion_3 addDependency:opertaion_2];
在这里就是opertaion_2会等opertaion_1执行完后再执行,后面以此类推
创建一个队列,默认就是并行队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
设置当前最大的执行数,可以控制是并行还串行,为1时就是串行
queue.maxConcurrentOperationCount = 1;
将任务添加到队列中任务就自动执行了
[queue addOperation:opertaion_1];
[queue addOperation:opertaion_2];
[queue addOperation:opertaion_3];
[queue addOperationWithBlock:^{
[self run_D];
}];
添加多个任务,并且可以设置等待完成
[queue addOperations:@[opertaion_1,opertaion_2,opertaion_3] waitUntilFinished:YES];
取消所有的任务
[queue cancelAllOperations];
取消指定的任务
[opertaion_1 cancel];
获取当前的队列
[NSOperationQueue currentQueue];
获取主队列
[NSOperationQueue mainQueue];
###6.2 其它方法
以上就是一些主要方法, 下面还有一些常用方法:
NSOperation
BOOL executing;
//判断任务是否正在执行BOOL finished;
//判断任务是否完成void (^completionBlock)(void);
//用来设置完成后需要执行的操作(void)cancel;
//取消任务(void)waitUntilFinished;
//阻塞当前线程直到此任务执行完 毕
NSOperationQueue
NSUInteger operationCount;
//获取队列的任务数(void)cancelAllOperations;
//取消队列中所有的任务(void)waitUntilAllOperationsAreFinished;
//阻塞当前线程直到此队列中的所有任务执行完毕[queue setSuspended:YES];
// 暂停queue[queue setSuspended:NO];
// 继续queue
##7 GCD和NSOperation的区别:
1、GCD的底层是用C来实现的,NSOperation底层从ios4开始也是⽤的GCD来实 现的;(底层实现)
2、在NSOperationQueue中,我们可以随时取消已经设定要准备执⾏的任务(当然,已经开始的任务就⽆法阻⽌了),⽽GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);(取消任务)
3、NSOperation能够⽅便地设置依赖关系,我们可以让⼀个Operation依赖于另⼀个Operation,这样的话尽管两个Operation处于同⼀个并行队列中,但前者会直到后者执行完毕后再执⾏;(依赖关系)
4、我们能将KVO应⽤用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能⽐GCD更加有效地掌控我们执⾏的后台任务;(监听任务的执⾏情况)
5、在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同⼀个并行队列中的任务区分先后地执行,⽽在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;(优先级) 6、我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提⾼整个代码的复⽤度,这⽐简单地将block任务排⼊执行队列更有自由度,能够在其之上添加更多自定制的功能。(代码复用)
Demo下载:
https://github.com/fuxinto/HfxDemo