前言
本博客撰写一下小蓝书的GCD
一、什么是GCD
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
例子:
//让处理在后台线程中执行
dispatch async(queue, ^{
/*
*长时间处理
*例如AR用画像识别*例如数据库访问
*/
/*
*长时间处理结束, 主线程使用该处理结果。
*/
//让处理在主线程中执行
dispatch_async(dispatch_get main_queue(), ^{
/*
*只在主线程可以执行的处理
*例如用户界面更新
*/
});
});
为什么使用GCD
- GCD 可用于多核的并行运算;
- GCD 会自动利用更多的 CPU 内核(比如双核、四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
二、任务和队列
任务:
就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。
同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力
队列:
队列是一种特殊的线性表,队列中允许插入操作的一端称为队尾,允许删除操作的一端称为队头,是一种先进先出的结构。在GCD里面队列是指执行任务的等待队列,是用来存放任务的。按照队列的结构特性,新任务总是插入在队列的末尾,而任务的执行总是从队列的对头输出,每读取一个任务,则从队列中释放一个任务。GCD的队列分为串行队列和并发队列两种,两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
串行队列:只开启一个线程,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务。
并发队列:可以让对个任务同时执行,也就是开启多个线程,让多个任务同时执行。
两者之间区别如下图所示:
三、GCD基本使用
GCD的使用很简单,首先创建一个队列,然后向队列中追加任务,系统会根据任务的类型执行任务。
队列的创建
队列的创建很简单,只需要调用dispatch_queue_create
方法传入相对应的参数便可。这个方法有两个参数:
- 第一个参数表示队列的唯一标识,可以传空。
- 第二个参数用来识别是串行队列还是并发队列。
DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT
表示并发队列。
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
GCD默认提供一种全局并发队列,调用 dispatch_get_global_queue
方法来获取全局并发队列。这个方法需要传入两个参数。
第一个参数是一个长整型类型的参数,表示队列优先级,有DISPATCH_QUEUE_PRIORITY_HIGH
、DISPATCH_QUEUE_PRIORITY_LOW
、DISPATCH_QUEUE_PRIORITY_BACKGROUND
、DISPATCH_QUEUE_PRIORITY_DEFAULT
四个选项,一般用 DISPATCH_QUEUE_PRIORITY_DEFAULT
。
第二个参数暂时没用,用 0 即可。
GCD默认提供了主队列,调用dispatch_get_main_queue
方法获取,所有放在主队列中的任务都会在主线程中执行。主队列是一种串行队列。
// 获取全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 主队
dispatch_queue_t mainQueue = dispatch_get_main_queue();
任务的创建
GCD调用dispatch_sync
创建同步任务,调用dispatch_async
创建异步任务。任务的内容都是在block代码块中。
//异步任务
dispatch_async(queue, ^{
//异步执行的代码
});
//同步任务
dispatch_sync(queue, ^{
//同步执行的代码
});
任务和队列的组合
创建的任务需要放在队列中去执行,同时考虑到主队列的特殊性,那么在不考虑嵌套任务的情况下就会存在同步任务+串行队列、同步任务+并发队列、异步任务+串行队列、异步任务+并发队列、主队列+同步任务、主队列+异步任务六种组合,下面我们来分析下这几种组合。
同步任务+串行队列
//同步任务+串行队列
- (void)syncTaskWithSerial {
NSLog(@"currentThread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"currentThread-1:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"currentThread-2:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"currentThread-3:%@", [NSThread currentThread]);
});
}
从上面代码运行的结果可以看出,并没有开启新的线程,任务是按顺序执行的。
异步任务+串行队列
//异步任务加串行队列
- (void)asyncTaskWithSeria{
NSLog(@"current thread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"current thread-1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-3:%@", [NSThread currentThread]);
});
NSLog(@"4");
}
从上面代码运行的结果可以看出,开启了一个新的线程,说明异步任务具备开启新的线程的能力,但是由于任务是在串行队列中执行的,所以任务是顺序执行的。
这里需要注意的是
4
这个输出,这其实意味着我们在我们创建的这个队列中异步执行了三个块,这也意味着这三个块将被提交到队列中,而当前线程会继续执行后面的代码(而4就是后面的代码)。由于队列是串行的,它们将按照它们被提交的顺序依次执行。
异步任务+并发队列
- (void)asyncTaskWithConcurrent{
NSLog(@"current thread:%@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"current thread-1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-3:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"current thread-4:%@", [NSThread currentThread]);
});
}
从上面代码的运行结果可以看出,生成了多个线程,并且任务是随机执行(并发执行)的。
主队列+同步任务
-(void)syncTaskWithMain{
NSLog(@"currentThread---%@",[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(@"4");
}
这段代码出现了错误,因为我们在主线程中执行syncTaskWithMain
方法,相当于把syncTaskWithMain
任务放到了主线程的队列中。而 同步执行会等待当前队列中的任务执行完毕,才会接着执行。当我们把任务
1追加到主队列中,任务 1
就在等待主线程处理完 syncTaskWithMain
任务。而syncTaskWithMain
任务需要等待任务 1,2,3
执行完毕,这样就形成了相互等待的情况,产生了死锁。
主队列+异步任务
-(void)asyncTaskWithMain{
NSLog(@"currentThread---%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
NSLog(@"任务1");
});
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
NSLog(@"任务2");
});
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
NSLog(@"任务3");
});
NSLog(@"4");
}
从上面代码的运行结果可以看出,虽然是异步任务,但是并没有开启新的线程,任然是在主线程中执行,并且任务是顺序执行(因为主队列是串行队列)的。
四、 Main Dispatch Queue & Global Dispatch Queue
- 对于串行队列,GCD 默认提供了:主队列(Main Dispatch Queue
主队列其实并不特殊。 主队列的实质上就是一个普通的串行队列,只是因为默认情况下,当前代码是放在主队列中的,然后主队列中的代码,有都会放到主线程中去执行,所以才造成了主队列特殊的现象 - 同时对于并行队列 GCD 默认提供了 全局并发队列(Global Dispatch Queue)
Global Dispatch Queue
有四个执行优先级。XNU内核管理的线程,将各自使用的Global Dispatch Queue
的执行优先级作为线程的优先级使用。在Global Dispatch Queue
里追加处理的时候应选择与处理内容对应的执行优先级的Global Dispatch Queue
不同优先级Queue获取方法
// global 高优先级
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// 默认优先级
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 低优先级
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
// 后台优先级
dispatch_queue_t globalDispatchQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
五、dispatch_set_target_queue函数
变更优先级
dispatch_queue_create 方法生成的Queue不论是串行还是并行队列,都是和globalQueue的默认优先级相同执行优先级的线程,对于变更优先级我们需要使用dispatch_set_target_queue函数
dispatch_set_target_queue(serialQueue, globalDispatchQueueBackGround);
第一个参数是需要变更优先级的参数,第二个参数指定了与那个优先级相同的队列。
添加执行层次
在必须将不可并行执行的处理追加到多个Serial Dispatch Queue 中时,如果使用dispatch set target queue 西数将目标指定为某 一个Serial DispatchQueue,即可防止处理并行执行。
六、dispatch_after
当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。
- (void)after {
NSLog(@"run -- 0");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 4秒后异步执行这里的代码...
NSLog(@"run -- 2");
});
}
七、Dispatch Group
Dispatch Group 是 Grand Central Dispatch(GCD)中的一种机制,用于追踪一组任务的完成情况。你可以使用 Dispatch Group 来等待一组任务全部完成后执行其他操作。
以下是一个简单的 Objective-C 代码示例,演示如何使用 Dispatch Group:
- (void)performTasksWithDispatchGroup {
// 创建一个 Dispatch Group
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 在 Dispatch Group 中异步执行任务1
dispatch_group_async(group, globalQueue, ^{
NSLog(@"Task 1 started");
// 模拟耗时任务
[NSThread sleepForTimeInterval:2];
NSLog(@"Task 1 completed");
});
// 在 Dispatch Group 中异步执行任务2
dispatch_group_async(group, globalQueue, ^{
NSLog(@"Task 2 started");
// 模拟耗时任务
[NSThread sleepForTimeInterval:3];
NSLog(@"Task 2 completed");
});
// 在 Dispatch Group 中异步执行任务3
dispatch_group_async(group, globalQueue, ^{
NSLog(@"Task 3 started");
// 模拟耗时任务
[NSThread sleepForTimeInterval:1];
NSLog(@"Task 3 completed");
});
// 等待 Dispatch Group 中的所有任务完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 所有任务完成后执行这里的代码
NSLog(@"All tasks completed");
// 注意:记得在不需要 Dispatch Group 时释放它
dispatch_release(group);
}
在上述代码中,我们使用 dispatch_group_create 创建了一个 Dispatch Group,并在其中异步执行了三个任务。然后,通过 dispatch_group_wait 等待 Dispatch Group 中的所有任务完成。最后,当所有任务完成后,输出 “All tasks completed”。
使用队列组的原因
无论向如何类型的Queue追加处理,Dispatch Group都可以监视这些处理直到执行结束,一旦执行结束,就可以将结束处理追加到DispatchQueue中。
八、GCD 栅栏方法:dispatch_barrier_async
dispatch_barrier_async 是 GCD(Grand Central Dispatch)中的一个方法,它用于在并发队列中插入一个“栅栏”(barrier)。栅栏的作用是在其前面的任务执行完毕后,再执行栅栏任务,然后再执行栅栏之后的任务。这可以用于确保在多线程环境中某些操作的顺序性和互斥性。
- (void)barrierTask {
dispatch_queue_t queue = dispatch_queue_create("com.shen.thread.demo", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"start");
dispatch_async(queue, ^{
NSLog(@"currentThread-1:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
dispatch_barrier_async(queue, ^{
NSLog(@"currentThread-2:%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
});
NSLog(@"pause");
dispatch_async(queue, ^{
NSLog(@"currentThread-3:%@", [NSThread currentThread]);
});
NSLog(@"end");
}
这段代码的输出是这样的:
异步任务1(dispatch_async)和栅栏任务2(dispatch_barrier_async)是在同一个并发队列中执行的,而且它们都是异步提交的,意味着它们会在主线程中执行而不会阻塞主线程。
因此,在主线程执行到 NSLog(@“pause”) 时,异步任务1和栅栏任务2已经被提交到并发队列中,但它们的执行是异步的,主线程会继续执行后面的代码,而不会等待它们执行完毕。
这也说明了异步任务1要等栅栏任务2结束后才能继续执行异步任务3
八、dispatch_sync & dispatch_async
dispatch_async意味着非同步,就是将制定的block非同步的追加到queue中, dispatch_async不会进行任何等待
dispatch_sync意味着同步,就是将制定的block同步的追加到queue中, dispatch_sync会在追加结束之前一直等待# 二、使用步骤
九、dispatch_apply-GCD 快速迭代
dispatch_apply 是 Grand Central Dispatch(GCD)中的一个方法,它可以用于在指定的队列上多次执行一个指定的块。dispatch_apply 是一个同步函数,会等待所有任务执行完毕后才会继续执行。他其实就像线程中的for循环。
dispatch_apply函数一般多用于并行队列,若是在串行队列使用其实for循环原理相似
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
- iterations: 要执行块的次数。
- queue: 任务提交的队列,可以是串行队列或并发队列。
- block: 要执行的任务块,接受一个参数,即当前的索引值。
/**
* 快速迭代方法 dispatch_apply
*/
- (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");
}
需要注意的是,dispatch_apply 是一个同步函数,会等待所有任务完成后才会继续执行后续代码。这也是为什么 “All tasks completed” 的输出会等待所有任务执行完毕后才显示。
十、dispatch_suspend & dispatch_resume
这两个函数可以随时挂起某个队列,等需要执行的时候恢复即可
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_suspend(queue);
dispatch_resume(queue);
十一、dispatch_semaphore-GCD 信号量
GCD 中的信号量是指 Dispatch Semaphore
:持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore
中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。
这里一定要理解Dispatch Semaphore
提供的三个方法
-
dispatch_semaphore_create
:创建一个 Semaphore 并初始化信号的总量 -
dispatch_semaphore_signal
:发送一个信号,让信号总量加 1 -
dispatch_semaphore_wait
:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后按需要使用信号量。
Dispatch Semaphore
在实际开发中主要用于:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
Dispatch Semaphore 线程同步
我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将异步执行任务转换为同步执行任务。
下面,我们来利用 Dispatch Semaphore 实现线程同步,将异步执行任务转换为同步执行任务。
/**
* semaphore 线程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
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;
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
Dispatch Semaphore 线程安全和线程同步(为线程加锁)
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
这里就涉及到之前了解过的售票问题,下面,我们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题。
场景:总共有 50 张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
先来看看不考虑线程安全的代码:
/**
* 非线程安全:不使用 semaphore
* 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
* 售卖火车票(非线程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { // 如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
break;
}
}
}
可以看到在不考虑线程安全,不使用 semaphore 的情况下,得到票数是错乱的,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。
当我们使用信号量对线程进行加锁时
/**
* 售卖火车票(线程安全)
*/
- (void)saleTicketSafe {
while (1) {
// 相当于加锁
dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1); dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { // 如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", (long)self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { // 如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}
这就保证了我们的线程一次只能处理一个任务,也就使我们的进程由并发队列+异步任务转换为了并发队列+同步任务
十二、dispatch_once
dispatch_once
是 Grand Central Dispatch(GCD)中的一个函数,用于确保某个代码块在程序运行过程中只被执行一次。它提供了一种线程安全的单例模式实现方式,可以防止多个线程同时执行初始化代码。
如此创建的单例模式只会被初始化一次文章来源:https://www.toymoban.com/news/detail-833000.html
先声明静态全局变量,只能在当前文件访问 ,单例对象的条件
#import "Person.h"
static id instance;
static id SingleEg;
// 懒汉
@implementation Person
+(id) instance {
static dispatch_once_t o;
dispatch_once(&o, ^{
if (instance == nil) {
instance = [[super alloc] init];
}
});
return instance;
}
// 饿汉
+ (id)_singleEg {
static dispatch_once_t o;
dispatch_once(&o, ^{
SingleEg = [[super alloc] init];
});
return SingleEg;
}
这样就保证即使在多线程环境下执行,单例也只会被初始化一次文章来源地址https://www.toymoban.com/news/detail-833000.html
到了这里,关于【iOS】GCD学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!