【iOS】GCD学习
- 前言
- 什么是GCD
- 任务和队列
- GCD基本使用
- GCD相关方法
-
- dispatch_set_target_queue
- dispatch_semaphore
- dispatch_after
- dispatch_once
- dispatch_apply
- dispatch_group_t
-
- [dispatch_group_async + dispatch_group_notify](#dispatch_group_async + dispatch_group_notify)
- [dispatch_group_enter / leave + dispatch_group_notify](#dispatch_group_enter / leave + dispatch_group_notify)
- dispatch_group_wait
- dispatch_barrier_async
- dispatch_source_t
- [disoatch_suspend & dispatch_resume](#disoatch_suspend & dispatch_resume)
- 总结
前言
GCD是iOS开发中非常重要的一个内容,在此作一个总结。
什么是GCD
GCD全称Grand Central Dispatch,是异步执行任务的技术之一 。一般将应用程序中记述的线程管理用的代码在系统及中实现。开发者只需要定义想执行的任务并追加到适当的调度队列中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样会比以前的线程更有效率。

仅一行代码就能表示让处理在后台线程中执行。
那么为什么使用GCD?
- GCD可用于多核的并行运算,即多任务在不同CPU核心上同时运行。
- GCD会自动利用更多的CPU内核(比如双核、四核),即自动利用多个线程。
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
任务和队列
任务:指在线程中执行操作的代码。在GCD中是放在Block中的。执行任务的方式有两种:
- 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力。
二者最主要的区别在于:是否具备开启新线程的能力。
队列:队列中允许插入操作的一端称为队尾,允许删除操作的一端称为队头。在GCD里队列是指执行任务的等待队列,用来存放任务。按照先进先出的特点,新任务总是插入在队列的末尾,而任务的执行总是从队列的队头输出。每读取一个任务,则从队列中释放一个任务。GCD的队列分为两种:
- 串行队列:只开启一个线程,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务。
- 并发队列:可以对多个任务同时执行,也就是开启多个线程,让多个任务同时执行。
二者最主要的区别在于:执行顺序不同,开启线程数不同。

GCD基本使用
队列的创建
队列创建只需要调用dispatch_queue_create方法,该方法有两个参数:
const char * _Nullable label:表示队列的唯一标识,可以传空。dispatch_queue_attr_t _Nullable attr:用来识别是串行队列还是并发队列。
objc
//串行队列
dispatch_queue_t serialQueue = dispatch_queue_create(@"myGCD", DISPATCH_QUEUE_SERIAL);
//并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create(@"myGCD", DISPATCH_QUEUE_CONCURRENT);
GCD默认一种全局并发队列,调用dispatch_get_global_queue方法来获取全局并发队列,该方法有两个参数:
intptr_t identifier:是一个长整型类型的参数,表示队列优先级,可用常量有:- DISPATCH_QUEUE_PRIORITY_DEFAULT:默认优先级,适用于普通任务。
- DISPATCH_QUEUE_PRIORITY_HIGH:高优先级,适用于高速任务、重要任务。
- DISPATCH_QUEUE_PRIORITY_LOW:低优先级,适用于不重要的后台任务。
- DISPATCH_QUEUE_PRIORITY_BACKGROUND:后台优先级,适用于日志写入、清理缓存等。
uintptr_t flags:写0即可。
objc
//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
GCD默认主队列,调用dispatch_get_main_queue方法获取,所有放在主队列中的任务都会在主线程中执行。主队列是一种串行队列。
objc
//主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
任务的创建
- 异步任务:调用dispatch_async创建。
objc
//异步任务
dispatch_async(concurrentQueue, ^{
NSLog(@"异步执行的代码");
});
- 同步任务:调用dispatch_sync创建。
objc
//同步任务
dispatch_sync(concurrentQueue, ^{
NSLog(@"同步执行的代码");
});
任务队列组合
在不考虑嵌套任务的情况下会存在以下几种情况:
我们通过[NSThread currentThread]获取当前线程对象的指针地址、线程编号和线程名字来测试一下。
- 同步任务+串行队列:
objc
-(void)syncTaskWithSerial {
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("syncTaskWithSerial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
}
同步任务+串行队列:没有开启新线程,任务是按顺序执行的。

- 同步任务+并发队列:
objc
-(void)syncTaskWithConcurrent {
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("syncTaskWithConcurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
}
同同步任务+串行队列一样,同步任务+并发队列:没有开启新线程,任务是按顺序执行的。

由此可见,并发队列能并发的前提是任务必须是异步派发。sync会强制任务在当前线程中执行,导致并发能力被禁用。
- 异步任务+串行队列:
objc
-(void)asyncTaskWithSerial {
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("asyncTaskWithSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
NSLog(@"继续执行后续代码");
}
异步任务+串行队列:开启了一个新线程,说明异步任务具备开启新线程的能力。但是由于任务是在串行队列中执行,因此任务是按顺序执行的。

输出"继续执行后续代码":因为是异步,所以不会阻塞当前线程,这意味着在我们创建的这个队列中执行了三个块,这三个块将被提交到队列中,而当前线程会继续执行后面代码。又由于是串行队列,它们将依照提交顺序依次执行。
- 异步任务+并发队列:
objc
-(void)asyncTaskWithConcurrent {
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("asyncTaskWithConcurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
NSLog(@"继续执行后续代码");
}
异步任务+并发队列:生成多个线程,并且任务是并发执行的,即多个任务按照随机顺序同时执行。

- 主队列+同步任务:
objc
-(void)syncTaskWithMain {
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
NSLog(@"继续执行后续代码");
}

我们会发现出现了错误。这是因为GCD的主队列底层就是一个串行队列。dispatch_get_main_queue会把任务放到主线程的队列中,而同步执行会使得主线程正在执行我们的方法,我们又让主线程同步等待一个主队列里的任务,即必须等待这个Block执行完,才能继续往下执行,而主队列里的任务又必须等待主线程空出来才能执行 。于是相互等待,就会产生死锁的情况。
- 主队列+异步任务
objc
-(void)asyncTaskWithMain {
NSLog(@"%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
NSLog(@"继续执行后续代码");
}
主队列+异步任务:由于主队列是串行队列,因此虽然是异步任务,但是并没有开启新线程,仍然是在主线程中,并且按照顺序执行的。

GCD相关方法
dispatch_set_target_queue
使用的两个目的:
- 改变队列的优先级
- 建立队列层级关系,防止并行执行
dispatch_queue_create 方法生成的队列不论是串行还是并发,都和全局并发队列globalQueue的默认优先级相同。对于变更优先级,我们需要使用dispatch_set_target_queue方法。
objc
dispatch_set_target_queue(<dispatch_object_t _Nonnull object>, <dispatch_queue_t _Nullable queue>);
参数1:目标队列,通常是创建的自定义队列(串行或并发),这个队列的属性将被改变。
参数2:参考队列,通常是全局队列或其他自定义队列,作为优先级和执行特性的参考标准,该队列的属性将被复制到目标队列。
dispatch_semaphore
是GCD提供的信号量同步机制,基于计数器实现线程间的同步、并发控制和资源保护,核心作用是通过控制"可用资源数"来协调多线程的执行节奏。
信号量:是多线程同步的核心工具。
- 用一个整数计数器表示当前可用资源数量。
- 线程想要使用资源时,先"申请"(计数器-1),若资源不足则排队等待。
- 线程用完资源后,"释放"(计数器+1),唤醒排队的线程继续执行。
用一个很形象的比喻来理解:
- 信号初始值:停车场总车位数,比如初始值为3,即有3个车位,3个可用资源。
- dispatch_semaphore_wait:车辆进停车场,车位-1。若车位>0,直接进;若<1,车辆排队。
- dispatch_semaphore_signal:车辆出停车场,车位+1,释放1个车位,唤醒排队的1辆车进入。
- 线程:车辆。线程竞争资源(车位),信号量协调执行顺序。
objc
dispatch_semaphore_t currSingal = dispatch_semaphore_create(value);//初始化信号量,小于0返回NULL
dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema);//发送信号量让信号量加1
dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout);//释放信号量,让总信号量减1,信号量小于0的时候就会一直等待,否则直接执行
这样使用的目的:
- 保持线程同步,将异步任务转化为同步执行任务。
- 保障线程的安全,为线程加锁。
核心应用:
- 并发队列里的异步任务顺序执行
objc
dispatch_semaphore_t currentSingal = dispatch_semaphore_create(0);
dispatch_queue_t que = dispatch_get_global_queue(0, 0);
dispatch_async(que, ^{
NSLog(@"执行任务1:%@", [NSThread currentThread]);
dispatch_semaphore_signal(currentSingal);
});
dispatch_semaphore_wait(currentSingal, DISPATCH_TIME_FOREVER);
dispatch_async(que, ^{
NSLog(@"执行任务2:%@", [NSThread currentThread]);
dispatch_semaphore_signal(currentSingal);
});
dispatch_semaphore_wait(currentSingal, DISPATCH_TIME_FOREVER);
dispatch_async(que, ^{
NSLog(@"执行任务3:%@", [NSThread currentThread]);
dispatch_semaphore_signal(currentSingal);
});

可以看的出,我们 dispatch_semaphore 的等待唤醒机制现在让并发队列中的异步任务(默认行为是任务被分发到不同线程并执行),从本该并行执行强制转换为严格按照任务1、任务2、任务3的顺序串行执行。
- 主线程等待异步任务完成(线程同步)
objc
-(void)semaphoreSync {
NSLog(@"当前线程:%@", [NSThread currentThread]);
NSLog(@"semaphore开始");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;//允许在异步block中修改
//异步派发任务到全局并发队列,任务在子线程执行,不阻塞当前线程
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];//任务1:模拟耗时操作,如网络请求、文件读写等
NSLog(@"1---%@",[NSThread currentThread]);
number = 100;
dispatch_semaphore_signal(semaphore);//发送信号量(+1),如果有线程在wait阻塞,会被唤醒
});
//等待信号量(-1),信号量变为-1,当前线程阻塞
//DISPATCH_TIME_FOREVER:表示永久等待,直到收到singal
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore结束,number = %d",number);
}

从结果我们可以看出:
打印结果可以拿到异步任务修改后的结果的原因是使用了dispatch_semaphore_wait。
- 信号量使得主线程派发异步任务后,调用wait,阻塞线程,等待子线程。
- 与此同时,子线程执行异步任务(sleep2秒),修改number为100,然后子线程发现线程在wait阻塞,被唤醒发信号。
- 主线程收到信号,继续往下执行,打印number(此时number已被修改)。
这样就实现了线程同步,将异步执行任务转换为同步执行。
- 控制最大并发数
本质是资源限流,避免并发线程过多而导致的内存/CPU过大。
objc
dispatch_semaphore_t currentSingal = dispatch_semaphore_create(3);//最多同时允许3个任务执行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(currentSingal, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务:%d", i);
sleep(1);
NSLog(@"完成任务:%d", i);
dispatch_semaphore_signal(currentSingal);
});
}

这里我们捋一下关键执行的逻辑:
每次最多同时允许3个任务执行。
- 第一批:信号量初始化为3,三个任务的wait依次将信号量减到0,无阻塞,所以同时执行,三个任务依次完成后,信号量加回到3。
- 第二批:i = 3、4、5的三个任务的wait依次减到0,无阻塞,直接执行,3个子线程同时执行 sleep(1) ,导致线程差异,导致其中某一任务(随机的)最先完成sleep。
- 后续任务:信号量的实时补位规则,i为4的任务完成后,信号量加1,此时阻塞队列中的任务6被唤醒,执行wait,信号量减为0,任务6开始执行。而任务3/5还在sleep,直到它们完成后,singal才会继续释放名额,唤醒任务7/8依次类推。
其中触发了优先级反转的警告。
优先级反转指的是高优先级任务因为资源被低优先级任务占用,结果被迫等待。而一个中等优先级任务却能先运行,导致优先级顺序被"反转"。
那么为什么会发生优先级反转呢?
假设有三个任务A、B、C,优先级分别是最高优先级、中、最低。任务C运行并持有一把锁,然后A优先级高于C,抢占了C开始运行。任务A也像申请那把锁,但锁被C持有,A被阻塞。系统中现可运行的任务是B和C,根据优先级,系统先调度任务B(中优先级)运行。这会导致任务C,也就是持锁者迟迟等不到机会来释放锁,任务A被C阻塞,中间的任务却先运行,形成了"优先级反转"。
那么为什么iOS中容易发生优先级反转呢?
关键原因来自信号量是公平队列FIFO。
- 信号量等待队列是FIFO,不按优先级:等待的线程进入顺序排队,被唤醒时不考虑优先级(QoS)。
- 系统调度是基于优先级的:高优先级线程调度更快,但如果它在等待信号量,仍然会被阻塞。
问题在于两套机制不一致。
总结一下,持锁者低优先级、竞争锁者高优先级,有一个中间优先级的任务不断占用CPU,调度+锁机制冲突共同导致低优先级任务影响高优先级,使高优先级被反转到更低级别,即优先级反转。所以,我们要尽量减少使用信号量做锁,避免iOS中出现优先级反转问题。
- 线程安全和线程同步(为线程加锁)
在多线程环境下,如果多个线程同时访问并修改同一份数据,就会出现线程不安全的问题;而当程序之间需要某种顺序协作时,就涉及线程同步。Dispatch Semaphore 是一个能同时解决这两个问题的工具,既能加锁保护数据共享数据(线程安全),也能控制线程等待(线程同步)。
这里我们用一个例子------售票问题来展示 Dispatch Semaphore 实现线程安全和线程同步:
objc
-(instancetype)init {
if (self = [super init]) {
self.ticketCount = 20;
self.ticketLock = dispatch_semaphore_create(1);
}
return self;
}
-(void)saleTicketSafe {
while (1) {
//加锁:信号量<=0进入阻塞等待
dispatch_semaphore_wait(self.ticketLock, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"剩余票数:%ld,窗口:%@", (long)self.ticketCount, [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.2];
} else {
NSLog(@"全部售完");
dispatch_semaphore_signal(self.ticketLock);
break;
}
//解锁:信号量+1,唤醒下一个线程
dispatch_semaphore_signal(self.ticketLock);
}
}
objc
self.manager = [[TicketManager alloc] init];
//开两个线程模拟两个售票窗口
NSThread *windowA = [[NSThread alloc] initWithTarget:self.manager selector:@selector(saleTicketSafe) object:nil];
NSThread *windowB = [[NSThread alloc] initWithTarget:self.manager selector:@selector(saleTicketSafe) object:nil];
windowA.name = @"窗口A";
windowB.name = @"窗口B";
//启动线程
[windowA start];
[windowB start];

从结果可以看出,我们实现了两个线程没有同时操作,且两个线程按顺序轮流执行,实现了线程同步;票数没有负数,说明锁保护了共享变量,实现了线程安全。
dispatch_after
dispatch_after表示延迟把任务加入队列中,而不是延迟执行。
objc
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after 的 block 被加入主队列");
});
//故意让主线程忙2秒
NSLog(@"主线程忙 2 秒...");
[NSThread sleepForTimeInterval:2];//阻塞主线程
NSLog(@"主线程忙完了");

dispatch_once
保证在整个App生命周期只执行一次block代码块中的代码,常用于单例。
objc
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"创建单例");
});
dispatch_apply
按照指定的次数将指定的任务追加到指定的队列中,快速迭代一组任务,并等待全部队列执行结束。通俗地说,就是多线程版本的for循环。主要用于需要快速并行处理大量相同类型任务的场景,如图像处理、数据处理或任何形式的批处理任务。
- 在串行队列中:队列中任务始终是一次执行一个,按顺序执行。
- 在并行队列/全局队列中:多线程并发执行,作用最大。
objc
//参数1:重复次数
//参数2:追加的队列
//参数3:执行任务
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"开始计算%@", [NSThread currentThread]);
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
dispatch_apply(10, queue, ^(size_t iteration) {
NSLog(@"计算在线程%@", [NSThread currentThread]);
sleep(1);
});
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"计算完成,耗时%.2f秒", end - start);

objc
NSLog(@"开始计算%@", [NSThread currentThread]);
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();//计算代码执行耗时
for (int i = 0; i < 10; i++) {
NSLog(@"计算在线程%@", [NSThread currentThread]);
sleep(1);
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"计算完成,耗时%.2f秒", end - start);

通过对比,我们可以看出 dispatch_apply 对并行队列是最有效的,极大的提高了效率,节约了时间。
- 在主队列中:死锁。

dispatch_group_t
用于监听一组异步任务何时完成。常用于以下几个场景:
- 多个网络请求全部完成后刷新UI
- 批量图片下载,全部完成后统一处理
dispatch_group_async + dispatch_group_notify
- async:异步完成后再回调,不会阻塞线程。
- notify:作用在于当group中所有任务完成后再触发执行block。
最常用且最适合多个请求完成后刷新UI。
objc
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"请求1完成");
});
dispatch_group_async(group, queue, ^{
NSLog(@"请求2完成");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});

dispatch_group_enter / leave + dispatch_group_notify
当我们不方便使用 dispatch_group_async 时(例如 URLSession回调、AFNetworking、多层嵌套异步等),就需要使用 dispatch_group_enter / leave 手动管理。
解释以上情况为什么不适合使用 dispatch_group_async。
首先我们知道 dispatch_group_async + dispatch_group_notify 的本质是我们自己把任务block提交到GCD队列中执行,block结束后,group自动 leave,当所有任务完成,notify 回调。也就是说,只有由GCD本身执行的同步区块,才能自动让group计数正常。那么看这几种情况:
- URLSession 回调:URLSession 的异步回调不在我们的 block 结束时执行。
objcdispatch_group_async(group, queue, ^{ NSURLSessionDataTask *task = [session dataTaskWithURL:url]; [task resume]; });看起来我们确实把任务加入到了group,但实际上任务并没有在这个block内完成。真正的执行顺序是block执行到
[task resume];时,让group以为它已经完成了,然而真正的网络请求在后台线程跑,很久之后才回调到 completionHandler,因此不适合。
- 第三方库AFNetworking:与URLSession 回调一样,也是异步回调,请求方法会立即返回,并不会等网络请求完成。
- 多层嵌套异步:同样,block只执行了最外层API的调用,让group误以为以为已完成任务,但实际上是没有完全完成。
objc
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(@"请求1完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"请求 2 完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});
- enter:表示"我有任务要执行了"
- leave:表示"任务执行完了"
enter必须与leave配对执行,否则group永远不会结束。
dispatch_group_wait
wait 会阻塞当前线程,知道任务完成或超时。
objc
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
NSLog(@"任务完成");
dispatch_group_leave(group);
});
NSLog(@"开始等待");
//group:需要等待的调度组
//timeout:等待的超时时间
//等待方式:
//DISPATCH_TIME_NOW:意味着不等待直接判定调度组是否执行完毕
//DISPATCH_TIME_FOREVER:会阻塞当前调度组,直到调度组执行完毕
//自定义时间:等待若干秒
//返回值:
//返回值为0:在指定时间内调度组完成了任务
//返回值不为0:在指定时间内调度组没有按时完成任务
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
if (timeout == 0) {
NSLog(@"按时完成任务");
} else {
NSLog(@"超时");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新界面");
});

总结一下notify和wait的区别:
- dispatch_group_notify:不阻塞线程,多任务完成后刷新UI。
- dispatch_group_wait:阻塞线程,必须等待结果才能继续执行。
dispatch_barrier_async
dispatch_barrier 是GCD中的栅栏函数。用于在并发队列上,插入一个"独占执行"的任务,使它前后任务不会并发执行。通俗地说,栅栏前的任务必须全部完成,栅栏任务自己必须独占执行(不会并发),栅栏后的任务必须等栅栏执行完才开始。这可以用于确保在多线程中某些操作的顺序性和互斥性。
- dispatch_barrier_async:可以控制队列中任务的执行顺序,前面的任务执行完毕后才会来这里。
- dispatch_barrier_sync:作用相同,但是同步任务,sync要求当前线程必须等它执行完才能继续,这样不仅阻塞了队列的执行,也阻塞了线程的执行,影响后面任务执行。
dispatch_barrier_async 主要有两种使用场景:串行队列、并发队列。
- 串行队列:
objc
-(void)serialBarrier {
dispatch_queue_t queue = dispatch_queue_create("serialBarrier", DISPATCH_QUEUE_SERIAL);
NSLog(@"开始:%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2秒的任务1:%@", [NSThread currentThread]);
});
NSLog(@"第一次结束:%@", [NSThread currentThread]);
//栅栏函数将队列中的任务进行分组
dispatch_barrier_async(queue, ^{
NSLog(@"栅栏任务:%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2秒的任务2:%@", [NSThread currentThread]);
});
NSLog(@"第二次结束:%@", [NSThread currentThread]);
}

因为串行任务本来就是一次只执行一个任务,不存在并发,所以看上去栅栏没有起到特殊作用。
- 并发队列:
objc
-(void)concurrentBarrier {
dispatch_queue_t queue = dispatch_queue_create(@"concurrentBarrier", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开始:%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2秒的任务1:%@", [NSThread currentThread]);
});
NSLog(@"第一次结束:%@", [NSThread currentThread]);
//栅栏函数将队列中的任务进行分组
dispatch_barrier_async(queue, ^{
NSLog(@"栅栏任务:%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束%@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2秒的任务2:%@", [NSThread currentThread]);
});
NSLog(@"第二次结束:%@", [NSThread currentThread]);
}

这样我们发现并发队列本应该在多线程同时执行,因为栅栏函数在延迟2秒的任务1执行时,挡住了下面的任务,这样就很好的控制了队列内任务执行的顺序。
barrier 最典型的用途是并发读、独占写,即读写锁。
objc
dispatch_queue_t rwQueue = dispatch_queue_create(@"rwQueue", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *array = [NSMutableArray array];
//写操作:用barrier独占执行
dispatch_barrier_async(rwQueue, ^{
[array addObject:@"数据"];
NSLog(@"写入完成");
});
//读操作:可以并发执行
dispatch_async(rwQueue, ^{
NSLog(@"读取任务1:%@", array);
});
dispatch_async(rwQueue, ^{
NSLog(@"读取任务2:%@", array);
});
这样读操作可以并发,提高性能;写操作被barrier独占,保证数据安全。
dispatch_source_t
dispatch_source_t 是GCD中最底层、最强大的事件处理机制之一,负责监听系统底层事件。本质上是一个"事件源",当某个事件发生时,它会将事件丢到我们指定的队列上执行回调。它可以用来监听底层事件(如文件变化、计时器、进程、网络等),主要用于计时操作。相比NSTimer、KVO、通知等,dispatch_source_t 不依赖RunLoop,精度更高。
在iOS开发中,一般使用NSTimer来处理定时逻辑,但NSTimer依赖RunLoop,且默认只在指定模式下触发。如果模式切换时,NSTimer就会挂机;又如果Runloop在阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期。因此NSTimer在计时上会有误差,并不是特别精确。
objc
-(void)testSource {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//dispatch_source是一种基本数据类型,用来监听一些底层系统事件
//DISPATCH_SOURCE_TYPE_TIMER:定时器事件源,用来生成周期性的通知或回调
//DISPATCH_SOURCE_TYPE_SIGNAL:监听信号事件源,当有UNIX信号发生时会通知
//UNIX信号:是UNIX/Linux系统(包括macOS/iOS)中用于进程间通信和系统向进程发送事件通知的一种机制,是操作系统与进程、进程与进程之间传递"紧急事件"的核心方式
//DISPATCH_SOURCE_TYPE_VNODE:监听文件事件源,当文件数据发生变化时会通知
//DISPATCH_SOURCE_TYPE_PROC:监听进程事件源,与进程相关的事件通知
//Mach:监听Mach端口事件源
//Mach端口:是Mach内核为进程/线程/内核组件之间传递消息(数据、指令、事件)设计的"通信接口",是iOS/macOS底层通信的基石
//Custom:监听自定义事件源
//常用API:
//dispatch_source_create:创建事件源
//dispatch_source_set_event_handler:设置数据源回调
//dispatch_source_merge_data:设置事件源数据
//dispatch_source_get_data:获取事件源数据
//dispatch_resume:继续
//dispatch_suspend:挂起
//dispatch_cancle:取消
//创建timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置timer首次执行时间、间隔、精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//设置timer事件回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"testSource");
});
//默认挂起状态,需要手动激活
dispatch_activate(timer);
}
disoatch_suspend & dispatch_resume
objc
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_suspend(queue);//把队列挂起,暂停调度
dispatch_resume(queue);//恢复队列
总结
以上就笔者关于GCD总结的相关内容,后续学习会继续完善。