我们看图片只是乐呵一下,程序猿思考问题差不多就是这个样子,
今天同事在线程通信这一块有点疑问,我们下面来分析一下,系统都提供给我们那些,其实我们都知道,但是很少去关注这些API,也正是这些API,来回在APP中去执行各种不同的线程和队列
一、常见的线程间通信 GCD
我们先来看一个系统的例子:
//开启一个全局队列的子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1. 开始请求数据
//...
// 2. 数据请求完毕
//我们知道UI的更新必须在主线程操作,所以我们要从子线程回调到主线程
dispatch_async(dispatch_get_main_queue(), ^{
//我已经回到主线程更新
});
});
如上所述,我们下面进行一个测试:
//自定义队列,开启新的子线程
dispatch_queue_t custom_queue = dispatch_queue_create("concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<10; i++) {
dispatch_async(custom_queue, ^{
NSLog(@"## 并行队列 %d ##",i);
//数据更新完毕回调主线程 线程之间的通信
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"### 我在主线程 通信 ##");
});
});
}
- 线程中延迟调用某个方法
//线程延迟调用 通信
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"## 在主线程延迟5秒调用 ##");
});
线程休眠几秒的方法
sleep(6);
: 这里是休眠6秒-
常用的线程通信方法有以下几种:(GCD)
- 需要更新UI操作的时候使用下面这个GCD的block方法
//回到主线程更新UI操作 dispatch_async(dispatch_get_main_queue(), ^{ //数据执行完毕回调到主线程操作UI更新数据 });
- 有时候省去麻烦,我们使用系统的全局队列:一般用这个处理遍历大数据查询操作
DISPATCH_QUEUE_PRIORITY_HIGH 全局队列高优先级 DISPATCH_QUEUE_PRIORITY_LOW 全局队列低优先级 DISPATCH_QUEUE_PRIORITY_BACKGROUND 全局队里后台执行队列 // 全局并发队列执行处理大量逻辑时使用 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ });
- 当在开发中遇到一些数据需要单线程访问的时候,我们可以采取同步线程的做法,来保证数据的正常执行
//当我们需要执行一些数据安全操作写入的时候,需要同步操作,后面所有的任务要等待当前线程的执行 dispatch_sync(dispatch_get_global_queue(0, 0), ^{ //同步线程操作可以保证数据的安全完整性 });
二、了解一下NSObject中的对象线程访问模式
-
我们介绍简单的perfermselecter选择器实现线程通信
//数据请求完毕回调到主线程,更新UI资源信息 waitUntilDone 设置YES ,代表等待当前线程执行完毕 [self performSelectorOnMainThread:@selector(dothing:) withObject:@[@"1"] waitUntilDone:YES];
-
如果需要执行到后台线程,则直接前往后台线程执行即可
//将当前的逻辑转到后台线程去执行 [self performSelectorInBackground:@selector(dothing:) withObject:@[@"2"]];
自己定义线程,将当前数据转移到指定的线程内去通信操作
//支持自定义线程通信执行相应的操作
NSThread * thread = [[NSThread alloc]init];
[thread start];
//当我们需要在特定的线程内去执行某一些数据的时候,我们需要指定某一个线程操作
[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];
//支持自定义线程通信执行相应的操作
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(testThread) object:nil];
[thread start];
//当我们需要在特定的线程内去执行某一些数据的时候,我们需要指定某一个线程操作
[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];
- 上面几种方法就是我们常用的对象调用常用的线程间通信,我们可以根据不同的情况,采取不同的线程执行状态.
增加一个特殊的线程常驻RunLoop 的做法
- 需求: 我们经常要用到常驻线程来请求数据,但是请求有时候在线程会退出,这个时候我们需要用一下方法:
//有时候需要线程单独跑一个RunLoop 来保证我们的请求对象存在,不至于会被系统释放掉
NSThread * runLoopThread = [[NSThread alloc]initWithTarget:self selector:@selector(entryThreadPoint) object:nil];
[runLoopThread start];
[self performSelector:@selector(handleMutiData) onThread:runLoopThread withObject:nil waitUntilDone:YES];
//给线程增加一个run loop 来保持对象的引用
- (void)entryThreadPoint{
@autoreleasepool {
[NSThread currentThread].name = @"自定义线程名字";
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
}
- (void)handleMutiData{
NSLog(@"## 我是跑在runloop的线程 ##");
}
最后测试截图如下,看下我们的线程是不是已经加入runloop 了
补充1:
- 有个地方需要补充以下: 请先看完下面的之后,再回头看这个地方。
需求: 如何动态的调整队列的优先级,执行层级,我们这里多加一个函数
// 设置目标队列 queue 既是target , object 是我们将要指定的dispatch 对象, 第一个参数要用自定义的队列,不能使用系统的全局队列,系统的队列层级我们无法修改
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
这个就是我们指定一个队列执行在目标队列上,和目标队列一个层级
我们看下代码如何调用:
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
dispatch_set_target_queue(queue1, queue2);
dispatch_async(queue2, ^{
NSLog(@"## 我是queue2 ----0###");
});
dispatch_async(queue1, ^{
NSLog(@"## 我是queue1 ------1##");
});
dispatch_async(queue2, ^{
NSLog(@"## 我是queue2 ----2 ##");
});
dispatch_async(queue1, ^{
NSLog(@"## 我是queue1 ------3 ##");
});
dispatch_async(queue1, ^{
NSLog(@"## 我是queue1 ------4 ##");
});
dispatch_async(queue2, ^{
NSLog(@"## 我是queue2 ---5 ##");
});
- 我们可以先猜想一下输出结果,会按照我们想要的结果显示么
答案:
2016-06-01 11:53:36.333 RunTimeModify[41214:6432344] ## 我是queue2 ----0###
2016-06-01 11:53:36.339 RunTimeModify[41214:6432344] ## 我是queue1 ------1##
2016-06-01 11:53:36.341 RunTimeModify[41214:6432344] ## 我是queue1 ------3 ##
2016-06-01 11:53:36.343 RunTimeModify[41214:6432344] ## 我是queue1 ------4 ##
2016-06-01 11:53:36.344 RunTimeModify[41214:6432344] ## 我是queue2 ----2 ##
2016-06-01 11:53:36.344 RunTimeModify[41214:6432344] ## 我是queue2 ---5 ##
相信大家发现问题了。
我们调整一下Target 的顺序:
dispatch_set_target_queue(queue2, queue1);
我们再来看一下输出:
2016-06-01 11:57:30.483 RunTimeModify[41297:6435226] ## 我是queue2 ----0###
2016-06-01 11:57:30.486 RunTimeModify[41297:6435226] ## 我是queue2 ----2 ##
2016-06-01 11:57:30.486 RunTimeModify[41297:6435226] ## 我是queue2 ---5 ##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------1##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------3 ##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------4 ##
以上只是简单的使用,我们不做什么的测试,了解就行:
以上这个地方,大家还会有个小疑问:连接在这里,stack 上找的How does dispatch_set_target_queue work?
大家看到这个或许就明白怎么工作的了
补充2:
谢谢 @激动的马 问题 ,还要增加一种通信方法
NSOperationQueue 系统自带的更新UI的方法 ,这种我不经常用,推荐GCD
//注意点 : 设计到UI的更新在主队列
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"### 我是在主队列执行的block ####");
}];
//这个只是用作判断当前线程是否是主线程
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"## 我是主线程 ##");
}else{
NSLog(@"## 我是子线程 ###");
}
补充三、
- GCD中阻塞线程之用法
//主要涉及到下面这个API,该API是保证自己的block会等待当前队列之前的block全部执行完毕,在执行自己的block,最后在执行后面的任务
void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
我们用代码简单的看一下调用顺序:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 全局队列异步执行 第1个任务 ##");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 全局队列异步执行 第2个任务 ##");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 全局队列异步执行 第4个任务 ##");
});
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 异步阻塞当前线程,会等待当前线程执行完毕 ###");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"### 我在全局队列线程内执行 第3个任务###");
});
* 我们看下控制台输出:
2016-06-01 14:04:13.973 RunTimeModify[42377:6489044] ## 全局队列异步执行 第1个任务 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489051] ## 全局队列异步执行 第2个任务 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489063] ## 全局队列异步执行 第4个任务 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489064] ## 异步阻塞当前线程,会等待当前线程执行 完毕 ###
2016-06-01 14:04:13.973 RunTimeModify[42377:6489065] ### 我在全局队列线程内执行 第3个任务###
大概可以看一下阻塞这个API是怎么用的,可以在项目总很好的处理我们的问题
- 我们在继续看下面的循环执行的block任务:
for (int i=0; i<5; i++) {
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 我是阻塞线程执行顺序 %d ###",i);
});
}
for (int i=0; i<5; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"### 我是全部异步队列任务 : %d ###",i);
});
}
我们看下控制台打印输出的日志系统:
2016-06-01 14:07:42.534 RunTimeModify[42437:6491161] ## 我是阻塞线程执行顺序 1 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491166] ## 我是阻塞线程执行顺序 0 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491168] ## 我是阻塞线程执行顺序 2 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491179] ## 我是阻塞线程执行顺序 3 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491180] ## 我是阻塞线程执行顺序 4 ###
2016-06-01 14:07:42.535 RunTimeModify[42437:6491181] ### 我是全部异步队列任务 : 0 ###
2016-06-01 14:07:42.535 RunTimeModify[42437:6491161] ### 我是全部异步队列任务 : 1 ###
2016-06-01 14:07:42.536 RunTimeModify[42437:6491166] ### 我是全部异步队列任务 : 2 ###
2016-06-01 14:07:42.536 RunTimeModify[42437:6491182] ### 我是全部异步队列任务 : 3 ###
2016-06-01 14:07:42.537 RunTimeModify[42437:6491168] ### 我是全部异步队列任务 : 4 ###
虽然都是异步任务,但是执行的很有规则。我们可以去尝试测试一下反过来的结果
- 可以测试下面这个执行顺序:
//测试一下,是不是阻塞API之前的任务必须要执行完毕,看下有什么问题
for (int i=0; i<5; i++) {
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"## 我是阻塞线程执行顺序 %d ###",i);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"### 我是全部异步队列任务 : %d ###",i);
});
}
- 经常遇到的问题:
- 网络请求到数据,但是UI没有及时更新。
- 在子线程的block内执行更新UI操作,控制台一般都会有错误线程输出的
- 如果只是逻辑的处理,无关UI,处理数据量较大,可以选择子线程处理。
本文暂时先介绍到这里,如果还想了解用信号量控制线程可以参考另外一篇博客:关于dispatch 下一遍补充了源的问题
iOS 关于dispatch_semaphore_t 和 dispatch_group_t 的简单实用,用于多网络异步回调通知