iOS 多线程之 GCD
前序:按顺序阅读更好
1. GCD的使用步骤
GCD 的使用步骤只有两步:
-
创建一个队列(串行队列或并发队列);
-
将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
1.1 队列的创建方法 / 获取方法
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("a.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("b.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 主队列的获取方法(主队列是特殊的串行队列)
dispatch_queue_t queue = dispatch_get_main_queue();
/*
* 全局并发队列的获取方法(全局并发队列是系统提供的并发队列)
* 第一个参数是设置优先级的,默认DISPATCH_QUEUE_PRIORITY_DEFAULT=0
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1.2 任务的创建方法
// 同步执行任务 创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务 创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
2. 死锁的情况
2.1 第一种
主队列+同步操作 主队列造成死锁的原因同第二种. 因为主队列其实是特殊的串行队列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@" gcd 前");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@" gcd 内");
});
NSLog(@" gcd 后");
}
2.2 第二种
在异步操作+串行队列的任务中,又嵌套了同步操作+当前的串行队列。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 异步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
这段代码会导致
串行队列中嵌套的任务
和串行队列中原有的任务
两者之间相互等待,阻塞了串行队列,最终造成了串行队列所在的线程(子线程)死锁。
2.3 第三种
在同步操作+串行队列的任务中,又嵌套了同步操作+当前的串行队列。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{ // 同步执行 + 串行队列
dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
});
3. GCD简单使用
3.1 处理耗时操作
由线程完成耗时操作,需要回到主线程,刷新UI
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
3.2 延迟执行任务 dispatch_after
dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。由于其内部使用的是dispatch_time_t管理时间,而不是NSTimer。所以如果在子线程中调用,相比performSelector:afterDelay,不用关心runloop是否开启。
注意: performSelector:afterDelay这个延迟函数,会在内部创建一个NSTimer,然后添加到当前线程的runloop中,也就是如果当前线程没有开启runloop这个方法会失效(子线程中需要手动启用runloop)。而performSelector只是一个单纯的消息发送,和时间没有一点关系,所以不需要添加到子线程的runloop也能执行。
/**
* 延时执行方法 dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
4. GCD高级应用
4.1 栅栏函数
dispatch_barrier_async
是一个栅栏函数,顾名思就是栅栏的作用,它会等待前面的任务结束,再执行后面的任务.
dispatch_barrier_async的简单应用
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
栅栏函数有 2 种,同步dispatch_barrier_sync
和异步dispatch_barrier_async
,下面来打印比较下它们的异同
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_async(dispatch_queue_create("mmmmmm", DISPATCH_QUEUE_CONCURRENT), ^{
[self logBarrierOrder];
});
}
- (void)logBarrierOrder {
NSLog(@"%@ >>>>> start ", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("test.barrier.queue", DISPATCH_QUEUE_CONCURRENT);
//异步函数 无序执行
NSLog(@"%@ >>>>> barrier 前面 A ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(5);
NSLog(@"%@ >>>>> 任务 1 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 前面 B ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"%@ >>>>> 任务 2 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 前面 C ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"%@ >>>>> 任务 3 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 前面 D ", [NSThread currentThread]);
dispatch_barrier_sync(queue, ^{
sleep(7);
NSLog(@"%@ ++++++ barrier ++++++ ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 后面 E ", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"%@ >>>>> 任务 4 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 后面 F ", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(10);
NSLog(@"%@ >>>>> 任务 5 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> barrier 后面 G ", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"%@ >>>>> 任务 6 ", [NSThread currentThread]);
});
NSLog(@"%@ >>>>> end ", [NSThread currentThread]);
}
//==================dispatch_barrier_sync执行结果如下==================//
dispatch_barrier_sync执行结果
>>>>> start
>>>>> barrier 前面 A
>>>>> barrier 前面 B
>>>>> barrier 前面 C
>>>>> barrier 前面 D
>>>>> 任务 3
>>>>> 任务 2
>>>>> 任务 1
++++++ barrier ++++++
>>>>> barrier 后面 E
>>>>> barrier 后面 F
>>>>> 任务 4
>>>>> barrier 后面 G
>>>>> end
>>>>> 任务 6
>>>>> 任务 5
//==================dispatch_barrier_sync执行结果如上==================//
//==================dispatch_barrier_async执行结果如下==================//
>>>>> start
>>>>> barrier 前面 A
>>>>> barrier 前面 B
>>>>> barrier 前面 C
>>>>> barrier 前面 D
>>>>> barrier 后面 E
>>>>> barrier 后面 F
>>>>> barrier 后面 G
>>>>> end
>>>>> 任务 3
>>>>> 任务 2
>>>>> 任务 1
++++++ barrier ++++++
>>>>> 任务 4
>>>>> 任务 6
>>>>> 任务 5
//==================dispatch_barrier_async执行结果如上==================//
- 共同点
- 先等任务1,2,3执行完,再执行barrier,执行完barrier,最后执行任务 4,5,6
- 不同点
- dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续执行写在barrier后面的任务(E、F、G),然后执行后面的任务(4,5,6)
- dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(E、F、G)插入到queue
栅栏函数注意点:
- 尽量使用自定义的并发队列,使用全局队列起不到栅栏函数的作用.使用全局队列时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列)
- 栅栏函数只能控制同一并发队列
- dispatch_barrier_async和dispatch_barrier_sync的异同:都可以阻止(此线程中)队列的执行,但是dispatch_barrier_sync也阻止了线程的执行
- dispatch_barrier_sync是在主线程中执行,dispatch_barrier_async是在子线程中执行。
- 在串行队列中,dispatch_barrier_sync和dispatch_barrier_async功能一样。dispatch_async会创建新的线程,如果是串行队列,那么只开启一个线程, 任务按照加入的顺序执行。
4.2 一次性代码(只执行一次): dispatch_once
/**
* 一次性代码(只执行一次)dispatch_once
*/
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
}
//dispatch_once在单例中的应用
+ (TMProgressHUD *)sharedInstance {
static TMProgressHUD *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self new];
});
return sharedInstance;
}
4.3 GCD 快速迭代方法:dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API.该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
-
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
-
如果是在并发队列进行异步执行,for 循环的做法是按顺序逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历(乱序)。
-
无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。
-
dispatch_apply加到主队列中会死锁(因为dispatch_apply实质是dispatch_sync函数和Dispatch Group的关联API)
dispatch_apply(6, dispatch_get_main_queue(), ^(size_t index){});
/**
* 快速迭代方法 dispatch_apply
* 遍历 0~5 这 6 个数字
* dispatch_get_global_queue 是全局并发队列
* 乱序遍历 ,但是apply---end最后执行
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,执行顺序也不定。但是 apply---end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。
4.4 组队列
dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
/**
* 队列组 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
dispatch_group_enter、dispatch_group_leave
- dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
- dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
- 当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
/**
* 队列组 dispatch_group_enter、dispatch_group_leave
*/
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程.
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
4.5 GCD 信号量:dispatch_semaphore
- dispatch_semaphore相关的3个函数
- dispatch_semaphore_create: 创建一个Semaphore并初始化信号的总量(不能小于 0);
- dispatch_semaphore_wait: 使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行;
- dispatch_semaphore_signal: 发送一个信号,让信号总量加1
- 发送信号(signal)与等待信号(wait)往往是成对出现的。
- dispatch_semaphore的实际应用
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
- 控制线程并发数
① 保持线程同步,将异步执行任务转换为同步执行任务
保持线程同步,也就是通过dispatch_semaphore控制异步任务一个执行完再执行另一个
-(void)dispatchSignal{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(queue, ^{
printf("run task 1 \n");
sleep(2);
printf("complete task 1 \n");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
printf("分割线1 \n");
//任务2
dispatch_async(queue, ^{
printf("run task 2 \n");
sleep(1);
printf("complete task 2 \n");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
printf("分割线2 \n");
//任务3
dispatch_async(queue, ^{
printf("run task 3 \n");
sleep(1);
printf("complete task 3 \n");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
printf("分割线3 \n");
//任务4
dispatch_async(queue, ^{
printf("run task 4 \n");
sleep(3);
printf("complete task 4 \n");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
printf("分割线4 \n");
printf("=====end======\n");
}
执行结果
run task 1
complete task 1
分割线1
run task 2
complete task 2
分割线2
run task 3
complete task 3
分割线3
run task 4
complete task 4
分割线4
=====end======
再举一个保持线程同步的例子: dispatch_semaphore_wait加锁阻塞了当前线程,dispatch_semaphore_signal解锁后当前线程继续执行。
//
-(void)dispatchSignal3{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block int number = 1;
dispatch_async(queue, ^{
number = 6;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"number--->%d",number);
}
② 保证线程安全,为线程加锁
首先创建全局变量,初始化信号量为1。在线程安全中可以将dispatch_semaphore_wait看作加锁,而dispatch_semaphore_signal看作解锁。
//1. 初始化全局变量
_semaphore = dispatch_semaphore_create(1);
-(void)asyncTask{
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);//当前信号量-1
printf("执行任务,这个任务需要加锁");
//线程休眠 C语言:sleep(1); OC:[NSThread sleepForTimeInterval:1];
[NSThread sleepForTimeInterval:1];
dispatch_semaphore_signal(_semaphore);//当前信号量+1
}
-(void)dispatchSignal4{
for (int i = 0; i < 100; i++){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self asyncTask];
});
}
}
③ 通过dispatch_semaphore_create的值控制线程并发数
/*
dispatch_semaphore_create的value表示,最多几个资源可访问
如果信号值设为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
如果设定的信号值为3,在这个方法里就是不限制线程执行了,因为一共才只有3个线程。
*/
-(void)dispatchSignal{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(2);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}
执行结果
run task 1
run task 2
complete task 2
complete task 1
run task 3
complete task 3
转载自:https://juejin.cn/post/6978124101386944542