线程概念
iOS程序中,主线程(又叫作UI线程)主要任务是处理UI事件,显示和刷新UI,(只有主线程有直接修改UI的能力)耗时的操作放在子线程(又叫作后台线程、异步线程)。在iOS中开子线程去处理耗时的操作,可以有效提高程序的执行效率,提高资源利用率。但是开启线程会占用一定的内存,(主线程的堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般不要同时开很多线程。
三种多线程变成的优缺点比较
- NSThread (抽象层次:低)
优点:轻量级,简单易用,可以直接操作线程对象
缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
2.NSOperation (抽象层次:中)
优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
3.GCD 全称Grand Center Dispatch (抽象层次:高)
优点:是 Apple 开发的一个多核编程的解决方法,简单易 用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。
队列
GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行
- The main queue(主线程串行队列):
与主线程功能相同,提交至 Main queue 的任务会在主线程中执行,
Main queue 可以通过 dispatch_get_main_queue() 来获取。- Global queue(全局并发队列):
全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别。
Global queue 可以通过调用 dispatch_get_global_queue 函数来获取(可以设置优先级)- Custom queue (自定义队列):
可以为串行,也可以为并发。
Custom queue 可以通过 dispatch_queue_create() 来获取;- Group queue(队列组):
将多线程进行分组,最大的好处是可获知所有线程的完成情况。
Group queue 可以通过调用dispatch_group_create()来获取
执行
执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力
- 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
- 异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力
队列和执行方式的组合,以及是都会开辟新的线程,用表格来表示一下一目了然
并发队列 | 串行队列 | 主队列 | |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
The main queue
//获取主线程串行队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//主线程串行队列同步执行任务,在主线程运行时,会产生死锁
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue,^{
NSLog("MainQueue");
});
//主线程串行队列异步执行任务,在主线程运行,不会产生死锁
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
NSLog("MainQueue");
});
Global queue(全局并发队列)
获取全局并发队列
//程序默认的队列级别,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//全局并发队列同步执行任务,在主线程执行,会导致页面卡顿。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_sync(globalQueue, ^{
sleep(2.0);
NSLog(@"sleep 2.0s");
});
NSLog(@"next task");
//控制台打印
2015-11-18 15:51:45.550 Whisper[33152:345023] current task
2015-11-18 15:51:47.552 Whisper[33152:345023] sleep 2.0s
2015-11-18 15:51:47.552 Whisper[33152:345023] next task
全局并发队列异步执行任务,在主线程运行,会开启新的子线程去执行任务,页面不会卡顿
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
sleep(2.0);
NSLog(@"sleep 2.0s");
});
NSLog(@"next task");
//控制台输出
2015-11-18 15:50:14.999 Whisper[33073:343781] current task
2015-11-18 15:50:15.000 Whisper[33073:343781] next task
2015-11-18 15:50:17.004 Whisper[33073:343841] sleep 2.0s
全局并发队列,异步执行多个任务。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
NSLog(@"先加入队列");
});
dispatch_async(globalQueue, ^{
NSLog(@"后加入队列");
});
NSLog(@"next task");
控制台输出如下:
2015-11-18 16:54:52.202 Whisper[39827:403208] current task
2015-11-18 16:54:52.203 Whisper[39827:403208] next task
2015-11-18 16:54:52.205 Whisper[39827:403309] 先加入队列
2015-11-18 16:54:52.205 Whisper[39827:403291] 后加入队列
异步线程的执行顺序是不确定的。几乎同步开始执行
全局并发队列由系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
Custom queue (自定义队列)
dispatch_time(dispatch_time_t when, int64_t delta);
参数注释:
第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始
第二个参数是延时的具体时间
延时1秒可以写成如下几种:
NSEC_PER_SEC----每秒有多少纳秒
dispatch_time(DISPATCH_TIME_NOW, 1NSEC_PER_SEC);
USEC_PER_SEC----每秒有多少毫秒(注意是指在纳秒的基础上)
dispatch_time(DISPATCH_TIME_NOW, 1000USEC_PER_SEC); //SEC---毫秒
NSEC_PER_USEC----每毫秒有多少纳秒。
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC*NSEC_PER_USEC);SEC---纳秒
获取自定义串行队列
//第一个参数是给这个queue起的标识,这个在调试的可以看到是哪个队列在执行,或者在crash日志中,也能做为提示。第二个是需要创建的队列类型,是串行的还是并发的
dispatch_queue_t serialQueue = dispatch_queue_create("com.customSerial.queue", DISPATCH_QUEUE_SERIAL);
获取自定义并发队列
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.conCurrentQueue.queue", DISPATCH_QUEUE_CONCURRENT);
Group queue (队列组)
回到主线程更新UI
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2-------%@",[NSThread currentThread]);
});
});
队列组
当遇到需要执行多个线程并发执行,然后等多个线程都结束之后,再做下一步操作,我们可以用队列组
// 全局并行队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_group_t groupQueue = dispatch_group_create();
NSLog(@"current task");
dispatch_group_async(groupQueue, globalQueue, ^{
NSLog(@"耗时操作1");
});
dispatch_group_async(groupQueue, globalQueue, ^{
NSLog(@"耗时操作2");
});
dispatch_group_notify(groupQueue, dispatch_get_main_queue(), ^{
NSLog(@"groupQueue中的任务 都执行完成,回到主线程更新UI");
});
NSLog(@"next task");
控制台打印
2015-11-19 13:47:55.117 Whisper[1645:97116] current task
2015-11-19 13:47:55.117 Whisper[1645:97116] next task
2015-11-19 13:47:55.119 Whisper[1645:97178] 耗时操作1
2015-11-19 13:47:55.119 Whisper[1645:97227] 耗时操作2
2015-11-19 13:47:55.171 Whisper[1645:97116] groupQueue中的任务 都执行完成,回到主线程更新UI
延时添加队列
dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"current task");
dispatch_after(delayTime3, mainQueue, ^{
NSLog(@"3秒之后添加到队列");
});
dispatch_after(delayTime2, mainQueue, ^{
NSLog(@"2秒之后添加到队列");
});
NSLog(@"next task");
控制台打印
2015-11-19 15:50:19.369 Whisper[2725:172593] current task
2015-11-19 15:50:19.370 Whisper[2725:172593] next task
2015-11-19 15:50:21.369 Whisper[2725:172593] 2秒之后添加到队列
2015-11-19 15:50:22.654 Whisper[2725:172593] 3秒之后添加到队列
dispatch_after只是延时提交block,并不是延时后立即执行,不能做到精确控制
栅栏方法
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
控制台打印
2016-09-03 19:35:51.271 GCD[11750:1914724] ----1-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----2-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----barrier-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914722] ----3-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914724] ----4-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
一次性代码(只执行一次) dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
**快速迭代方法 **
通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的方法dispatch_apply,使我们可以同时遍历。比如说遍历0~5这6个数字,for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd------%@",index, [NSThread currentThread]);
});
控制台打印
2016-09-03 19:37:02.250 GCD[11764:1915764] 1------<NSThread: 0x7fac9a7029e0>{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915885] 0------<NSThread: 0x7fac9a614bd0>{number = 2, name = (null)}
2016-09-03 19:37:02.250 GCD[11764:1915886] 2------<NSThread: 0x7fac9a542b20>{number = 3, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915764] 4------<NSThread: 0x7fac9a7029e0>{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915884] 3------<NSThread: 0x7fac9a76ca10>{number = 4, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915885] 5------<NSThread: 0x7fac9a614bd0>{number = 2, name = (null)}
dispatch_apply在给定的队列上多次执行某一任务
- dispatch_apply函数的功能:把一项任务提交到队列中多次执行,队列可以是串行也可以是并行,dispatch_apply不会立刻返回,在执行完block中的任务后才会返回,是同步执行的函数。
- dispatch_apply正确使用方法:为了不阻塞主线程,一般把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
//第一个参数,3--block执行的次数
//第二个参数,applyQueue--block任务提交到的队列
//第三个参数,block--需要重复执行的任务
dispatch_apply(3, applyQueue, ^(size_t index) {
NSLog(@"current index %@",@(index));
sleep(1);
});
NSLog(@"dispatch_apply 执行完成");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"回到主线程更新UI");
});
});
NSLog(@"next task");
控制台打印
2015-11-19 16:24:45.015 Whisper[4034:202269] current task
2015-11-19 16:24:45.016 Whisper[4034:202269] next task
2015-11-19 16:24:45.016 Whisper[4034:202347] current index 0
2015-11-19 16:24:45.016 Whisper[4034:202344] current index 1
2015-11-19 16:24:45.016 Whisper[4034:202345] current index 2
2015-11-19 16:24:46.021 Whisper[4034:202347] dispatch_apply 执行完成
2015-11-19 16:24:46.021 Whisper[4034:202269] 回到主线程更新UI
嵌套使用dispatch_apply会导致死锁。
dispatch semaphore信号计数器,可以进行更细微的控制
信号计数器,可以进行更细微的控制,类似于马路信号的手旗,可通过时举起手旗,不可通过放下手旗。技术信号为0表示等待,技术信号大于等于1减去1而不等待。
假设我们有如下场景,需要对可变数组存入10000个对象,并且在存入之前要做do something处理,do somthing是个很耗时的操作。所以可以使用多线程异步并发的操作:
dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
/*
do something
*/
[array addObject:[NSNumber numberWithInt:i]];
});
}
使用了上面的操作,可以异步并发的执行添加对象到数组,可以明显提高效率。但是,同一时刻操作同一块array的内存空间会导致内存问题,出现资源竞争,于是对代码进行如下优化:
dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
/*
do something
*/
//信号==0等待,>=1减1不等待进入下一步代码
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
//信号 +1
dispatch_semaphore_signal(semaphore);
});
}
dispatch_suspend/dispatchp_resume
// 挂起指定队列
dispatch_suspend(queue);
// 恢复指定队列
dispatchp_resume(queue);
dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一个block,延时5秒打印。
dispatch_async(queue, ^{
sleep(5);
NSLog(@"After 5 seconds...");
});
//提交第二个block,也是延时5秒打印
dispatch_async(queue, ^{
sleep(5);
NSLog(@"After 5 seconds again...");
});
//延时一秒
NSLog(@"sleep 1 second...");
sleep(1);
//挂起队列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延时10秒
NSLog(@"sleep 10 second...");
sleep(10);
//恢复队列
NSLog(@"resume...");
dispatch_resume(queue);
打印输出
dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一个block,延时5秒打印。
dispatch_async(queue, ^{
sleep(5);
NSLog(@"After 5 seconds...");
});
//提交第二个block,也是延时5秒打印
dispatch_async(queue, ^{
sleep(5);
NSLog(@"After 5 seconds again...");
});
//延时一秒
NSLog(@"sleep 1 second...");
sleep(1);
//挂起队列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延时10秒
NSLog(@"sleep 10 second...");
sleep(10);
//恢复队列
NSLog(@"resume...");
dispatch_resume(queue);
AFNetworking+GCD处理并发问题
GCD的leave和enter 我们利用dispatch_group_t创建队列组,手动管理group关联的block运行状态,进入和退出group的次数必须匹配。
//1.创建队列组
dispatch_group_t group = dispatch_group_create();
//2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.添加请求
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
[HomeRequest getPointBuyAllConfigurationStrategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {
dispatch_group_leave(group);
} failuer:^(NSInteger code, NSString *message) {
dispatch_group_leave(group);
}];
});
dispatch_group_async(group, queue, ^{
dispatch_group_enter(group);
[HomeRequest getStockLeverRiskStockCode:_buyingStrategyModel.stockCode strategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {
dispatch_group_leave(group);
} failuer:^(NSInteger code, NSString *message) {
dispatch_group_leave(group);
}];
});
//4.队列组所有请求完成回调刷新UI
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"model:%f",_buyingStrategyModel.leverrisk);
});
GCD的信号量dispatch_semaphore_t这种方式有点类似于通知模式,是利用监听信号量来发送消息以达到并发处理的效果
//创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
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, ^{
[HomeRequest getPointBuyAllConfigurationStrategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {
dispatch_semaphore_signal(semaphore);
} failuer:^(NSInteger code, NSString *message) {
dispatch_semaphore_signal(semaphore);
}];
});
dispatch_group_async(group, queue, ^{
[HomeRequest getStockLeverRiskStockCode:_buyingStrategyModel.stockCode strategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {
dispatch_semaphore_signal(semaphore);
} failuer:^(NSInteger code, NSString *message) {
dispatch_semaphore_signal(semaphore);
}];
});
dispatch_group_notify(group, queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"信号量为0");
});