【iOS】 GCD小结
iOS GCD学习总结
文章目录
- iOS GCD学习总结
- 前言
- GCD简介
- GCD的任务和队列
- 任务
- 队列
- 创建一个队列
- 并发队列和串行队列与sync 和 async 的关系
- 同步异步函数
- 串行并行队列
- GCD的基本使用
- 同步函数 加 串行队列
- 异步函数 加 串行队列
- 同步函数 加 并行队列
- 异步函数 加 并行队列:
- 死锁
- 概念
- 同步函数 加 主队列
- GCD相关方法
- dispatch_semaphore
- 相关方法
- 作用
- 设置一个最大开辟的线程数
- 加锁
- dispatch_after
- dispatch_once
- dispatch_apply
- dispatch_group
- dispatch_barrier_async/sync 方法
- 多读单写问题:
- dispatch_suspend/dispatch_resume
- 小结
前言
GCD是iOS开发中非常重要的一个内容,笔者今天对于这部分内容做一个介绍,
GCD简介
GCD(Grand Central Dispatch)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上,执行的并发任务。
线程池模式:
线程池工作队列已经装满了,且在线程池中正在运行的线程数小于可
最大线程数
(有一个核心线程任务可能刚刚执行完),则新进来的任务,会直接创建非核心线程马上执行线程池工作队列已经装满了,且在线程池中正在运行的线程数小于可最大线程数
(有一个核心线程任务可能刚刚执行完),则新进来的任务,会直接创建非核心线程马上执行
GCD的好处具体如下:
- GCD可以用于多核的并行计算
- GCD会自动利用更多的CPU内核
- GCD会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
- 程序员质用告诉GCD要执行什么任务,不用编写任何管理线程管理代码
GCD的任务和队列
任务
任务就是执行操作的意思,换句话说就是我们在线程执行的那段代码,在GCD中是放在block
的
- 同步执行 ( sync )
- 同步添加任务到指定的队列中,在添加的任务执行结束前,会一直等待,知道里面的任务完成之后再继续执行
- 只能在当前线程中执行任务,不具备开启新线程的能力
- 异步执行(async)
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行后面的任务
- 可以在新的线程中执行任务,具备开启新线程的能力
dispatch_sync(dispatch_queue_t _Nonnull, ^{//同步任务代码}) // 同步执行,等待队列中的任务完成后就开始执行
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
队列
队列(Dispatch Queue) : 这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进线出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,从队列中释放一个任务。
- 串行队列
- 每次只有一个任务被执行,让一个任务一个接着一个的去执行.
- 并发队列
- 可以让多个任务并发执行,可以开启多个线程,并且同时执行任务
并发队列的并发功能只有在异步函数下才可以体现出来
创建一个队列
dispatch_queue_t serialQueue = dispatch_queue_create("nanxunTestSerialQueue", DISPATCH_QUEUE_SERIAL);//创建一个串行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("nanxunTestConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);//创建一个并发队列
对于于串行队列,GCD提供了一种特殊的串行队列:主队列(Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();//获取串行队列
对于并发队列,GCD默认提供了全局并发队列(Global Dispatch Queue)
dispatch_queue_t uqe = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获取全局并发队列
并发队列 | 手动创建的串行队列 | 主队列 | |
---|---|---|---|
同步 (sync) | 没有开启新线程, 串行执行任务 | 没有开启新线程, 串行执行任务 | 没有开启新线程, 串行执行任务 |
异步 (async) | 有开启新线程的能力, 并发执行 | 有开启新线程, 串行执行任务 | 没有开启新线程,串行执行任务 |
- 使用sync函数往串行队列里面加入任务,会产生死锁.
- 并发功能只有在异步函数才会生效
并发队列和串行队列与sync 和 async 的关系
同步异步函数
同步函数(sync)和异步函数(async),只能决定能否开启新的线程执行任务,不能决定函数是串行执行还是并行执行
同步:
- 在当前线程中执行,不具备开启新线程的能力
- 队列后面的内容需要等到同步函数返回后才可以继续执行
异步
- 拥有开创新线程的能力`,但是不一定会开启新线程
- 后面的内容不需要等异步函数返回后执行,后面的内容可以直接执行,所以需要开启新线程,因此具备开启新线程的能力
同步函数: 同步函数会阻塞当前函数的返回, 异步函数会立即执行,执行下面的代码
同步函数后面的内容要等到同步函数返回才可以执行,异步函数后面的内容不需要等异步函数返回才执行,可以直接执行,异步函数里面的内可能需要开启一个新线程来执行
串行并行队列
主要影响任务的执行方式.能不能开启新线程取决于上面的函数
并发:多个任务并发执行
串行:一个任务执行完毕后才去执行下一个任务
- 如果传入的是一个并发队列,那就并发执行任务
- 如果传入的是一个手动创建的串行队列,那就在子队列串行执行
- 如果传入的是主队列,就在主线程串行执行
- 如果传入的是一个主队列,那么就不会开启新线程
GCD的基本使用
同步函数 加 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("nanxunTestSerialQueue", DISPATCH_QUEUE_SERIAL);dispatch_sync(serialQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"1 %d == %@", i, [NSThread currentThread]);}});dispatch_sync(serialQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"2 %d == %@", i, [NSThread currentThread]);}});
输出结果:
结论:同步串行队列没有开启新的线程也没有有异步执行.
异步函数 加 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("nanxunTestSerialQueue", DISPATCH_QUEUE_SERIAL);dispatch_async(serialQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"1 %d == %@", i, [NSThread currentThread]);}});dispatch_async(serialQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"2 %d == %@", i, [NSThread currentThread]);}});
这里开启了一个新的线程来执行这里的代码,但是因为串行队列还是顺序执行
同步函数 加 并行队列
dispatch_queue_t serialQueue = dispatch_queue_create("nanxunTestSerialQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_sync(serialQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"1 %d == %@", i, [NSThread currentThread]);}});dispatch_sync(serialQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"2 %d == %@", i, [NSThread currentThread]);}});
这里可以发现同步函数没有开启线程的能力,也没有体现并发的特点/
异步函数 加 并行队列:
dispatch_queue_t concurentQueue = dispatch_queue_create("nanxunTestSerialQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurentQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"1 %d == %@", i, [NSThread currentThread]);}});dispatch_async(concurentQueue, ^{for (int i = 0; i < 3; i++) {NSLog(@"2 %d == %@", i, [NSThread currentThread]);}});
这里创建了新的线程的同时还体现了并发执行的特点
死锁
概念
- 死锁是因为资源有限以及线程的交错执行导致的
死锁产生的四个必要条件
- 互斥访问,再有互斥访问的一个情况下,线程才会出现等待
- 持有并等待.线程持有一些资源,并等待一些资源
- 资源非抢占:,一旦一个资源被持有,除非持有者主动放弃.否则其他竞争者都无法获取这个资源
- 循环等待:循环等待是指存在一系列线程T0,T1,TnT0等待T1,T1等待T2,T2等待Tn这样便出现了一个循环等待
这时候我们在理解在GCD里面出现的线程问题
这里我们质用理解同步执行和异步执行的概念:
同步会阻塞当前函数的返回,异步函数会立即返回,直接执行下面的代码
因为同步函数会阻塞当前的一个函数,等待这个函数返回之后才会执行任务,因为同步函数会等待函数的返回.
队列上是放任务,而线程是去执队列上的任务
同步函数 加 主队列
这里就出现一个死锁:
然后Xcode放出一个报错,这里我们分析一下去主队列里面的任务
- 主线程中任务执行: 任务1, sync, 任务3
- 主队列: viewDidLoad, 任务
主队列中首先有viewDidLoad这个任务.而这个任务在到中途的时候给这个主队列添加了一个新的任务,这个任务就会等待我们的viewDidLoad这个函数执行完毕才会继续执行任务.但是viewDidLoad这个函数被sync
这个函数给阻塞了.所以导致了这个函数他不能继续执行,所以出现了死锁
GCD相关方法
dispatch_semaphore
什么是信号量,这里笔者引用一段学长的博客,笔者认为他的解释比较妥当.
简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看 门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开 车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用
相关方法
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的时候就会一直等待,否则就可以正常执行
使用的前提是:清楚需要处理哪一个线程等待,哪一个线程又继续执行,然后使用信号量
作用
-
保持线程同步,将异步执行任务转化为同步执行任务
-
保障线程的安全,为线程加锁
先展示第一块内容:
dispatch_semaphore_t currSingal = dispatch_semaphore_create(0);// 创建信号量,如果小于0则会返回NULLdispatch_queue_t que = dispatch_get_global_queue(0, 0);dispatch_async(que, ^{NSLog(@"执行任务1. %@", [NSThread currentThread]);dispatch_semaphore_signal(currSingal);});dispatch_semaphore_wait(currSingal, DISPATCH_TIME_FOREVER);dispatch_async(que, ^{NSLog(@"执行任务2. %@", [NSThread currentThread]);dispatch_semaphore_signal(currSingal);});dispatch_semaphore_wait(currSingal, DISPATCH_TIME_FOREVER);dispatch_async(que, ^{NSLog(@"执行任务3. %@", [NSThread currentThread]);dispatch_semaphore_signal(currSingal);});
这里我通过信号量实现了一个任务逐步执行的一个效果,可以看下面输出的结果,这几个任务都是按顺序输出的:
设置一个最大开辟的线程数
在我们如果要下载很多图片的话,并发异步进行,每一个下载都会开辟一个新线程,可以我们又担心太多线程会道指内存开销太大,以及线程的上下文切换给我们的cpu带来的开销太大导致的问题所以我们要设置一下对应的一个线程最大数量就可以了
dispatch_semaphore_t currSingal = dispatch_semaphore_create(3);// 创建信号量,如果小于0则会返回NULLdispatch_queue_t queue = dispatch_get_global_queue(0, 0);for (int i = 0; i < 10; i++) {dispatch_async(queue, ^{dispatch_semaphore_wait(currSingal, DISPATCH_TIME_FOREVER);NSLog(@"执行任务, %d", i);sleep(1);NSLog(@"完成任务, %d", i);dispatch_semaphore_signal(currSingal);});}
在诶里可以看出我们这里最开始的三个异步操作,剩下的需要同步等待,只有释放出来的操作才可以进行异步操作
这里其实出现了一个优先级反转的问题,这里我们看一下:如果打印我们的NSThread,他会出现什么:
这个报错锁我们的User-initiated这个线程在等待交底优先级的线程.出现了一个优先级反转的内容:
这里笔者先介绍一下什么是优先级反转:
在程序执行时多个任务可能会对同—份数据产生 竞争’因此任务会使用锁来保护共享数据°假设现在有3个任务A、B`C’它们的优先 级为A>B>C任务C在运行时持有一把锁’然后它被高优先级的任务A抢占了(任 务C的锁没有被释放)。此时任务A恰巧也想申请任务C持有的锁’但是申请失败,因 此进人阻塞状态等待任务C放锁。此时’任务B、C都处于可以运行的状态,由于任务 B的优先级高于C,因此B优先运行°综合观察该情况’就会发现任务B好像优先级高 于任务A’先于任务A执行。
如同上面这张图片一样,我们就会出现一个优先级反转的问题
这里我们来理解一下上面为什么会出现一个优先级反转的问题:
- 前三个任务先进入工作状态,
- 后面4 - 10 个任务被阻塞
- 系统会在这些阻塞的线程中,挑选一个来唤醒 (注意这里并不一定考虑线程的一个优先级)
- 这时候如果某一个优先级比较低的线程被唤醒会开始执行
- 此时另一个高优先级的线程也在等待信号量,去不许等这个低优先级的线程先完成,这样那个自高优先级的线程就被低优先级的线程卡住了,这就是我们这里说的优先级反转的问题
信号量调度是公平队列(FIFO/不考虑 QoS)+ 系统线程调度是根据优先级(QoS)来的,两者机制不一致,所以可能造成高优先级线程因为信号量资源被低优先级线程占用而“被阻塞”。
所以在iOS中为了避免我们出现优先级反转的问题,尽量减少采用信号量的方式
加锁
我们还可以通过GCD解决一个线程加锁的问题
我们解决一下火车售票问题:
- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = UIColor.whiteColor;
// NSLog(@"%@ %@ %@", [[NSString stringWithFormat:@"abdcfeghijk"] class], [@"123" class], [@"12" class]);
// NSString* string = [NSString stringWithString:@"123123"];
// NSLog(@"%@", string);dispatch_queue_t queue1 = dispatch_queue_create("beijingTest", DISPATCH_QUEUE_SERIAL);dispatch_queue_t queuq2 = dispatch_queue_create("shanghaiTest", DISPATCH_QUEUE_SERIAL);dispatch_semaphore_t lock = dispatch_semaphore_create(50);self.ticketCount = 50;__weak typeof(self) weakSelf = self;dispatch_async(queue1, ^{[weakSelf sellTicket:lock];});dispatch_async(queue1, ^{[weakSelf sellTicket:lock];});
}
- (void) sellTicket:(dispatch_semaphore_t)lock {while (1) {dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);if (self.ticketCount > 0) {NSLog(@"当前的线程 : %@ 当前的票数:%ld", [NSThread currentThread], self.ticketCount);self.ticketCount -= 1;dispatch_semaphore_signal(lock);} else {NSLog(@"票已经卖完了");dispatch_semaphore_signal(lock);break;}}
}
这样我们通过给两个线程加锁的方式,保证它访问统一快内存是安全的,符合逻辑的,不会出现数据上的错误
dispatch_after
在指定事件多少秒之后执行某个任务,可以用GCD的方法来实现:
需要注意的是:dispatch_after方法并不是在指定时间之后才开始执行处理的, 而是在指定时间之后将任务追加到主队列中严格来说这个事件不一定是绝对准确的
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"延迟了--%@", [NSThread currentThread]);});
dispatch_once
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{code to be executed once});
单例的时候常用的方式,这里可以保证代码在程序运行的过程中只会被执行一次,并且及时在多线程环境下,dispatch_once也可以保证线程安全
dispatch_apply
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
在串行对垒中使用dispatch_apply
那么就和for循环一样,按顺序同步执行,但是这样就体现不出快速迭代的特点了"
我们可以利用并发对列进行一个异步执行.比方说遍历0~5这结构数字,for循环是在一个线程中遍历.dispatch_apply
可以在多个线程中同时遍历多个数字
无论是在串行队列还是在并发队列中,dispatch_apply都会等待全部任务执行完毕,这点就可以理解为同步操作
dispatch_apply
是同步函数:它会阻塞当前线程,直到所有迭代任务完成。
所以他也会和之前的一个sync
函数一样会出现死锁
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);NSLog(@"apply函数开始");dispatch_apply(6, queue, ^(size_t iteration) {NSLog(@"%zd---%@", iteration, [NSThread currentThread]);});NSLog(@"apply函数结束");
这个函数有两个参数:
- 传入的一个循环次数
- 传入一个派发队列
- 如果串入一个并发队列,就是并发的执行6次
- 如果传入一个主队列(或者是当前的串行队列),会死锁
- 如果传入一个新创建的串行队列,会同步循环执行,和for循环没区别
有一个实际的应用场景:处理图像中的像素点时,并行执行:
引用自:GCD函数
- (UIImage *)applyGrayscaleFilterToImage:(UIImage *)image {// 获取图像的宽度和高度CGSize size = image.size;CGFloat width = size.width;CGFloat height = size.height;// 创建一个新的 bitmap 图像上下文CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedFirst);CGColorSpaceRelease(colorSpace);// 在上下文上绘制原始图像CGRect rect = CGRectMake(0, 0, width, height);CGContextDrawImage(context, rect, image.CGImage);// 使用 dispatch_apply 并行处理每个像素dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);dispatch_apply(width * height, queue, ^(size_t index) {// 计算当前像素的坐标size_t x = index % (size_t)width;size_t y = index / (size_t)width;// 获取当前像素的 RGBA 值uint8_t *pixelData = (uint8_t *)CGBitmapContextGetData(context) + y * CGBitmapContextGetBytesPerRow(context) + x * 4;uint8_t red = pixelData[0];uint8_t green = pixelData[1];uint8_t blue = pixelData[2];// 计算灰度值并更新像素uint8_t gray = (uint8_t)((red * 0.3 + green * 0.59 + blue * 0.11));pixelData[0] = gray;pixelData[1] = gray;pixelData[2] = gray;});// 从上下文中创建一个新的 UIImage 并返回CGImageRef newImage = CGBitmapContextCreateImage(context);UIImage *filteredImage = [UIImage imageWithCGImage:newImage];CGImageRelease(newImage);CGContextRelease(context);return filteredImage;
}
dispatch_group
我希望等几个任务执行完毕后,再执行最后一个任务时。(控制执行顺序)
比方说我们有两个网络申请,等这两个网络申请都结束我们才回调主线程刷新UI
这里就可以采用队列组的一个概念:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self fetchOrderList:^(BOOL success) {dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 所有数据获取完成,进行最终处理NSLog(@"All data fetched!");
});
//或者写成下面的内容:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));if (dispatch_group_wait(group, time) == 0) {NSLog(@"事事件完成");} else {NSLog(@"事件没完成");}
dispatch_group_t group = dispatch_group_create();
这个是创建一个线程组dispatch_group_enter(group);
进入线程组dispatch_group_leave(group);
退出线程组dispatch_group_wait
是一个暂停当前线程,等待指定的group中的任务执行完成后,才会继续向下执行,在这里,我们设置了一个 10 秒的超时时间,如果在 10 秒内所有任务都完成,则打印事件完成.dispatch_group_notify
这个函数不会想上面的函数那样子阻塞当前的线程,而是采用异步回调的一个方式来调用
所以为了不影响用户体验我的建议是采用dispatch_group_notify
这个函数来进行一个异步回调.
dispatch_barrier_async/sync 方法
我们先从这个图片来理解我们的Dispatch Barrier这个函数,这里我们可以通过设置一个barrier Task任务来拦截前后的一个任务,这样可以在多线程中保证一个属性在多线程环境下多读单写的一个安全,下面就来讲一下这个函数来解决多读单写:
派发 Barrier 任务是在处理并发队列时充当串行队列样式对象的一组函数。使用 GCD的 Barrier API 可以确保提交的任务是指定队列在特定时间内唯一执行的任务。
这意味着在调度 Barrier 任务之前提交到队列的所有任务必须在 Barrier 任务执行之前完成。当轮到 Barrier 任务时,GCD 确保队列在此期间不执行任何其他任务。当 Barrier 任务完成后,队列会返回到其默认实现。GCD 提供同步和异步 Barrier 函数。
多读单写问题:
多读单写的要求:
- 可以允许读操作并发。
- 读操作和写操作同一时间只能执行一个。
- 写操作和写操作同一时间只能执行一个。
先认识一下**dispatch_barrier_async
与 dispatch_barrier_sync
的区别**
特性 | dispatch_barrier_async | dispatch_barrier_sync |
---|---|---|
提交方式 | 异步提交,函数立即返回,不阻塞当前线程。 | 同步提交,函数会阻塞当前线程,直到屏障任务完成。 |
适用场景 | 不需要立即等待结果的操作(如后台写入)。 | 需要确保屏障任务完成后才继续后续逻辑(如数据同步)。 |
线程资源占用 | 不占用当前线程资源,适合主线程或高优先级队列。 | 可能阻塞当前线程,需谨慎使用以避免性能问题。 |
任务执行顺序 | 屏障任务的执行顺序与其他任务一致,但独占队列。 | 可能阻塞当前线程,需谨慎使用以避免性能问题。 |
这个主要是为了实现一个多读单写,我们可以采用栅栏函数:
@interface Person : NSObject {
@private NSInteger num;
}@property (nonatomic, strong) NSString* string;
@property (nonatomic, copy) NSString* name;@end
@implementation Person {@privatedispatch_queue_t _queue;NSString *_name;}
static void* personContext = &personContext;
static void* studentContext = &studentContext;- (instancetype)init
{self = [super init];if (self) {//self->_dataAry = [NSArray array];_queue = dispatch_queue_create("nanxunTest", DISPATCH_QUEUE_CONCURRENT);//NSLog(@"%@ %@", [self class], [self superclass]);}return self;
}
- (void)setName:(NSString *)name {__weak typeof(self) weakSelf = self;dispatch_barrier_async(_queue, ^{__strong typeof(self) strongSelf = weakSelf;strongSelf->_name = [name copy];NSLog(@"写操作注入名字%@", _name);NSLog(@"写操作结束");});
}
- (NSString *)name {__block NSString* tmp;__weak typeof(self) weakSelf = self;dispatch_sync(_queue, ^{ //保证读操作是同步返回的.__strong typeof(self) strongSelf = weakSelf;tmp = strongSelf->_name;});return tmp;
}//下面是main函数操作
Person* person = [[Person alloc] init];dispatch_queue_t que = dispatch_queue_create("nanxun", DISPATCH_QUEUE_CONCURRENT);dispatch_async(que, ^{person.name = @"123";NSLog(@"写操作开始");});dispatch_async(que, ^{NSLog(@"%@", person.name);});dispatch_async(que, ^{NSLog(@"%@", person.name);});dispatch_async(que, ^{NSLog(@"%@", person.name);});dispatch_async(que, ^{person.name = @"321";});dispatch_async(que, ^{NSLog(@"%@", person.name);});
这样的打印结果是:
为什么会出现这样的现象笔者总结一下每一个步骤用的函数的原因:
-
主函数采用一个并发队列进行读写,其实就是一个多线程并发读的一个操作,这允许多个任务同时提交到队列中
-
而我们的set方法采用了一个
sync
的方法是为了保证读的操作是原子性的,不会出现同时访问问题:读取操作需要立即返回当前数据的值。如果使用异步提交(
dispatch_async
),调用者无法直接获取结果,因为异步任务会在后台执行,而函数会立即返回空值(或未更新的值)。读并发的原因是我们在之前采用了一个异步队列开辟了多个线程同时读取,然后里面的sync其实是同步返回. -
写操作采用了
dispatch_barrier_async
这个函数可以让我们不需要立即等待结果的操作.直接在后台写入这部分内容.但是会阻塞后面提交的一个读任务,保证读和写操作是互斥的.
这样就实现了一个读写互斥
dispatch_suspend/dispatch_resume
dispatch_suspend暂时挂起指定dispatch_queue
dispatch_resume回复执行的dispatch_queue
这些函数对已执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。
小结
笔者简单总结有关于GCD的相关内容,之后会学习有关于锁的部分