1.基本概念
进程: 一个具有一定独立功能的程序关于某个数据集合的一次运行活动。可以理解成一个运行中的应用程序。
线程: 程序执行流的最小单元,线程是进程中的一个实体。
同步: 只能在当前线程按先后顺序依次执行,不开启新线程。
异步: 可以在当前线程开启多个新线程执行,可不按顺序执行。
队列: 装载线程任务的队形结构。
并发: 线程执行可以同时一起进行执行。
串行: 线程执行只能依次逐一先后有序的执行。
一个进程可以有多个线程,也可以有多个队列
2.iOS多线程对比
NSThread
每个NSThread对象对应一个线程,真正最原始的线程。
1)优点:NSThread 轻量级最低,相对简单。
2)缺点:手动管理所有的线程活动,如生命周期、线程同步、睡眠等。NSOperation
自带线程管理的抽象类。
1)优点:自带线程周期管理,操作上可更注重自己逻辑。
2)缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
1)优点:最高效,避开并发陷阱。
2)缺点:基于C实现。选择小结
1)简单而安全的选择NSOperation实现多线程即可。
2)处理大量并发数据,又追求性能效率的选择GCD。
3)NSThread本人选择基本上是在做些小测试上使用,当然也可以基于此造个轮子。
3.场景选择
图片异步加载
这种常见的场景是最常见也是必不可少的。异步加载图片有分成两种来说明一下。
- 第一种,在UI主线程开启新线程按顺序加载图片,加载完成刷新UI。
- 第二种,依然是在主线程开启新线程,顺序不定地加载图片,加载完成个字刷新UI。
创作工具上的异步
这个跟上边任务调度道理差不多,只是为了丰富描述,有助于“举一反三”效果。如下描述的是app创作小说。
- 场景一,app本地创作10个章节内容未成同步服务器,接着同时发表这10个章节将产生的一系列动作,其中上传内容,获取分配章节Id,如果后台没有做处理最好方式做异步按顺序执行。
- 场景二,app本地创作列表中有3本小说要发表,如果同时发表创作列表中的3本小说,自然考虑并行队列执行发表。
4.使用方法
NSThread的使用
- (void)dynamicCreateThread
{
//创建NSThread 动态
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
thread.threadPriority = 1;
[thread start];
}
- (void)staticCreateThread
{
//创建静态线程
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
- (void)implicitCreateThread
{
//隐式创建线程
[self performSelectorInBackground:@selector(loadImage) withObject:self];
}
NSThread的拓展认识
①获取当前线程
NSThread *current = [NSThread currentThread];
②获取主线程
NSThread *main = [NSThread mainThread];
③暂停当前线程
[NSThread sleepForTimeInterval:2];
④线程之间通信
//在指定线程上执行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
//在主线程上执行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
//在当前线程执行操作
[self performSelector:@selector(run) withObject:nil];
NSOperation
主要的实现方式:结合NSOperation和NSOperationQueue实现多线程编程。
实例化NSOperation的子类,绑定执行的操作。
创建NSOperationQueue队列,将NSOperation实例添加进来。
系统会自动将NSOperationQueue队列中检测取出和执行NSOperation的操作。
使用NSOperation的子类实现创作线程。
①.NSInvocationOperation创建线程。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
//[invocationOperation start];//直接会在当前线程主线程执行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];[queue addOperation:invocationOperation];
②.NSBlockOperation创建线程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[self loadImageSource:imgUrl];
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:blockOperation];
GCD?
Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法,GCD 是一个替代诸如 NSThread, NSOperationQueue, NSInvocationOperation 等技术的很高效和强大的技术。
分发队列种类(dispatch queue)
①.UI主线程队列 main queue
dispatch_get_main_queue()
②.并行队列global dispatch queue
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
这里的两个参数得说明一下:第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可
③.串行队列serial queues
dispatch_queue_create("minggo.app.com", NULL);
6种多线程实现
①后台执行线程创建
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self loadImageSource:imgUrl1];
});
②UI线程执行(只是为了测试,长时间加载内容不放在主线程)
dispatch_async(dispatch_get_main_queue(), ^{
[self loadImageSource:imgUrl1];
});
③一次性执行(常用来写单例)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self loadImageSource:imgUrl1];
});
④并发地执行循环迭代
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
NSLog(@"循环执行第%li次",i);
[self loadImageSource:imgUrl1];
});
⑤延迟执行
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self loadImageSource:imgUrl1];
});
⑥自定义dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("minggo.app.com", NULL);
dispatch_async(urls_queue, ^{
[self loadImageSource:imgUrl1];
});
对比多任务执行
异步加载图片是大部分app都要面对的问题,那么加载图片是按循序加载完之后才刷新UI呢?还是不安顺序加载UI呢?显然大部分的希望各自加载各自的图片,各自刷新。以下就是模拟这两种场景。
①先后执行,加载两张图片为例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image1 = [self loadImage:imgUrl1];
UIImage *image2 = [self loadImage:imgUrl2];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview1.image = image1;
self.imageView2.image = image2;
});
});
②并行队列执行,也是以加载两张图片为例
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{ dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image1 = [self loadImage:imgUrl1];
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image2 = [self loadImage:imgUrl2];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageview1.image = image1;
self.imageView2.image = image2;
});
});
①中等到两张图片加载完成后一起刷新,②就是典型的异步并行的例子,不需要理会各自图片加载的先后问题,完成加载图片刷新UI即可。从加载图片中来说,第①种不太合适使用,但是对于在上边场景选择中的创作工具来说有很大的好处,首先得异步进行,然后异步中有得按顺序执行几个任务,比如上传章节内容。因此,我们可以灵活考虑使用这两多线程任务执行方式,实现各种场景。