多线程
同步、异步、并发、串行
同步和异步决定能否开启新的线程
同步: 在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
并发队列 | 手动创建串行队列 | 主队列 | |
---|---|---|---|
同步(sync) | 没有开启新线程<br />串行执行任务 | 没有开启新线程<br />串行执行任务 | 没有开启新线程<br />串行执行任务 |
异步(async) | 开启新线程<br />并发执行任务 | 开启新线程<br />串行执行任务 | 没有开启新线程<br />串行执行任务 |
使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)
1. dispatch_after
dispatch_after
表示在某队列中的block延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2秒后输出");
});
2. dispatch_once
dispatch_once
保证在App运行期间,block中的代码只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//创建单例、method swizzled或其他任务
});
3. dispatch_apply
dispatch_apply
将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束。
模拟for循环,当要对NSArray类对象的所有元素执行处理时,不必一个一个的编写for循环部分
//1.创建NSArray类对象
NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
//2.创建一个全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.通过dispatch_apply函数对NSArray中的全部元素进行处理,并等待处理完成
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
});
//4.会在上面执行完之后执行
NSLog(@"done");
4. dispatch_group_t
应用场景:多个接口请求之后刷新页面
1. dispatch_group_async
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
// 添加异步任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1-%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2-%@", [NSThread currentThread]);
}
});
// 等前面的任务执行完毕后,会自动执行这个任务
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3-%@", [NSThread currentThread]);
}
});
2. dispatch_group_enter & dispatch_group_leave
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求二完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
3.dispatch_group_wait
使用
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
-
group:需要等待的调度组
-
timeout:等待的超时时间(即等多久)
- 设置为
DISPATCH_TIME_NOW
意味着不等待直接判定调度组是否执行完毕 - 设置为
DISPATCH_TIME_FOREVER
则会阻塞当前调度组,直到调度组执行完毕
- 设置为
-
返回值:为
long
类型- 返回值为0——在指定时间内调度组完成了任务
- 返回值不为0——在指定时间内调度组没有按时完成任务
- (void)test {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求二完成");
dispatch_group_leave(group);
});
//不等待,立即判断是否执行完毕
long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
// 等到队列组执行完毕
// long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//指定时间内是否执行完毕
// long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
NSLog(@"timeout=%ld", timeout);
if (timeout == 0) {
NSLog(@"按时完成任务");
} else {
NSLog(@"超时");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});
}
5. 栅栏函数dispatch_barrier_sync & dispatch_barrier_async
先执行栅栏前任务
,再执行栅栏任务
,最后执行栅栏后任务
应用场景:同步锁
-
dispatch_barrier_async
:前面的任务执行完毕才会来到这里 -
dispatch_barrier_sync
:作用相同,但是这个会堵塞线程,影响后面的任务执行
注意点:
- 使用
全局队列
起不到栅栏函数
的作用 - 使用
全局队列
时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列) - 栅栏函数只能控制同一并发队列
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("HJTest", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开始——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1——%@", [NSThread currentThread]);
});
NSLog(@"第一次结束——%@", [NSThread currentThread]);
dispatch_barrier_async(queue, ^{
NSLog(@"----------栅栏任务----------%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束——%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"延迟1s的任务2——%@", [NSThread currentThread]);
});
NSLog(@"第二次结束——%@", [NSThread currentThread]);
}
由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
6. dispatch_semaphore_t
-
dispatch_semaphore_create()
:创建信号量 -
dispatch_semaphore_wait()
:等待信号量,信号量减1。当信号量< 0
时会阻塞当前线程,根据传入的等待时间决定接下来的操作——如果永久等待将等到信号(signal)
才执行下去 -
dispatch_semaphore_signal()
:释放信号量,信号量加1。当信号量>= 0
会执行wait之后的代码
创建信号量时传入值为1时,可以通过两次才堵塞,传入值为2时,可以通过三次才堵塞
以下代码也可以使用栅栏函数实现
// 创建信号量
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("HJTest", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"当前%d----线程%@", i, [NSThread currentThread]);
// 打印任务结束后信号量解锁
dispatch_semaphore_signal(sem);
});
// 由于异步执行,打印任务会较慢,所以这里信号量加锁
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
7. dispatch_source
1.定义及使用
dispatch_source
是一种基本的数据类型,可以用来监听一些底层的系统事件
-
Timer Dispatch Source
:定时器事件源,用来生成周期性的通知或回调 -
Signal Dispatch Source
:监听信号事件源,当有UNIX信号发生时会通知 -
Descriptor Dispatch Source
:监听文件或socket事件源,当文件或socket数据发生变化时会通知 -
Process Dispatch Source
:监听进程事件源,与进程相关的事件通知 -
Mach port Dispatch Source
:监听Mach端口事件源 -
Custom Dispatch Source
:监听自定义事件源
主要使用的API:
-
dispatch_source_create
: 创建事件源 -
dispatch_source_set_event_handler
: 设置数据源回调 -
dispatch_source_merge_data
: 设置事件源数据 -
dispatch_source_get_data
: 获取事件源数据 -
dispatch_resume
: 继续 -
dispatch_suspend
: 挂起 -
dispatch_cancle
: 取消
@property (nonatomic, strong) dispatch_source_t timer;
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.创建timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.设置timer首次执行时间,间隔,精确度 leeway:期望容忍值
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//4.设置timer事件回调
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCDTimer");
});
//5.默认是挂起状态,需要手动激活
dispatch_resume(_timer);
使用dispatch_source
自定义定时器注意点:
-
GCDTimer
需要强持有
,否则出了作用域立即释放,也就没有了事件回调 -
GCDTimer
默认是挂起状态,需要手动激活 -
GCDTimer
没有repeat
,需要封装来增加标志位控制 -
GCDTimer
如果存在循环引用,使用weak+strong
或者提前调用dispatch_source_cancel
取消timer -
dispatch_resume
和dispatch_suspend
调用次数需要平衡 -
source
在挂起状态
下,如果直接设置source = nil
或者重新创建source
都会造成crash。正确的方式是在激活状态
下调用dispatch_source_cancel(source)
释放当前的source