GCD其他方法
- dispatch_semaphore (信号量)
- 什么是dispatch_semaphore(信号量)?
- dispatch_semaphore主要的三个方法
- dispatch_semaphore主要作用
- 线程安全
- 线程同步
- dispatch_after
- dispatch_time_t 两种形式
- GCD 一次性代码(只执行一次):dispatch_once
- GCD 快速迭代方法:dispatch_apply
- GCD 队列组:dispatch_group
- dispatch_group_notify
- dispatch_group_wait
- dispatch_group_enter、dispatch_group_leave
- dispatch_barrier_async/sync栅栏方法
- 多读单写问题:
- dispatch_barrier_async/sync 的区别
- dispatch_suspend / dispatch_resume
- GCD线程同步方法总结
- 面试题
- 1. 多读单写问题
dispatch_semaphore (信号量)
什么是dispatch_semaphore(信号量)?
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看 门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开 车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
dispatch_semaphore主要的三个方法
GCD信号量机制主要涉及到以下三个函数:
// 创建信号量,value:初始信号量数 如果小于0则会返回NULL
dispatch_semaphore_create(long value); // 发送信号量是的信号量+1
dispatch_semaphore_signal(dispatch_semaphore_t deem);//可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
dispatch_semaphore主要作用
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
- 异步转同步
- 众所周知并发队列中的任务,由异步线程执行的顺序是不确定的,两个任务分别由两个线程执行,很难控制哪个任务先执行完,哪个任务后执行完。但有时候确实有这样的需求:两个任务虽然是异步的,但仍需要同步执行。
实际案例:
比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {__block NSArray *tasks = nil;dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {tasks = dataTasks;} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {tasks = uploadTasks;} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {tasks = downloadTasks;} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];}dispatch_semaphore_signal(semaphore);}];dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);return tasks;
}
使用举例:
//信号量初始化必须大于等于0, 因为dispatch_semaphore_wait 执行的是-1操作。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//创建异步队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{sleep(1);NSLog(@"执行任务: A");//让信号量+1dispatch_semaphore_signal(semaphore);
});//当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程.(相当于加锁)
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{sleep(1);NSLog(@"执行任务: B");//让信号量+1(相当于解锁)dispatch_semaphore_signal(semaphore);
});//当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{sleep(1);NSLog(@"执行任务: C");dispatch_semaphore_signal(semaphore);
});
多次运行的结果都是A,B,C顺序执行,让A,B,C异步执行变成同步执行,dispatch_semaphore相当于加锁效果
- 设置最大开辟的线程数
- 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。
这里出现优先级反转了
//设置最大开辟的线程数为3dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);//创建一个并发队列dispatch_queue_t queue = dispatch_get_global_queue(0, 0);//开启的是10个异步的操作,通过信号量,让10个异步的最多3个m,剩下的同步等待for (NSInteger i = 0; i < 10; i++) {dispatch_async(queue, ^{//当信号量为0时候,阻塞当前线程dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSLog(@"执行任务 %ld", i);sleep(1);NSLog(@"完成当前任务 %ld", i);//释放信号dispatch_semaphore_signal(semaphore);});}
执行结果:
执行任务 0
执行任务 2
执行任务 1
完成当前任务 2
完成当前任务 1
完成当前任务 0
执行任务 3
执行任务 4
执行任务 5
完成当前任务 3
完成当前任务 5
完成当前任务 4
执行任务 6
执行任务 8
执行任务 7
完成当前任务 7
完成当前任务 8
完成当前任务 6
执行任务 9
完成当前任务 9
执行结果表明:最开始执行3个异步操作,剩下的同步等待,只有释放出来才可以剩下的操作才可以进行异步操作
- 加锁机制
- 当你的信号设置为1的时候就相当于加锁
- (void)viewDidLoad {[super viewDidLoad];NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程NSLog(@"semaphore---begin");semaphoreLock = dispatch_semaphore_create(1);self.ticketCount = 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 saleTicketSafe];});dispatch_async(queue2, ^{[weakSelf saleTicketSafe];});
}- (void)saleTicketSafe {while (1) {// 相当于加锁dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);if (self.ticketCount > 0) { //如果还有票,继续售卖self.ticketSurplusCount--;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);[NSThread sleepForTimeInterval:0.2];} else { //如果已卖完,关闭售票窗口NSLog(@"所有火车票均已售完"); // 相当于解锁dispatch_semaphore_signal(semaphoreLock);break;} // 相当于解锁dispatch_semaphore_signal(semaphoreLock);}
}
可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore 机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。
线程安全
线程安全:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程同步
线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。
(经典火车票售卖模型)
dispatch_after
我们经常会遇到这样的需求:在指定时间(例如 3 秒)之后执行某个任务。可以用 GCD 的dispatch_after 方法来实现。
需要注意的是:dispatch_after 方法并不是在指定时间之后才开始执行处理,而 是在指定时间之后将任务追加到主队列中 。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。
/*** 延时执行方法 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]); // 打印当前线程});
}
dispatch_time_t 两种形式
- 基于
dispatch_time
函数,表示从当前时间开始的一个时间点
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
使用:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{NSLog(@"dispatch_time");
});
时间单位:
#define NSEC_PER_SEC 1000000000ull 1秒
#define NSEC_PER_MSEC 1000000ull 1毫秒
#define USEC_PER_SEC 1000000ull 1秒
#define NSEC_PER_USEC 1000ull 1微秒
时间点:
#define DISPATCH_TIME_NOW (0ull) 0->现在
#define DISPATCH_TIME_FOREVER (~0ull) -1->永远
dispatch_time_t定义
typedef uint64_t dispatch_time_t; unsigned long long 64位无符号长整形
- 基于
diapatch_walltime
函数,表示根据当前设备硬件的绝对时间
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
dispatch_time_t walltime = dispatch_walltime(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC));
dispatch_after(walltime, dispatch_get_main_queue(), ^{NSLog(@"dispatch_walltime");
});
一般来讲客户端是不相信本地硬件时钟的,所以通常使用dispatch_time就好了
GCD 一次性代码(只执行一次):dispatch_once
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。
/*** 一次性代码(只执行一次)dispatch_once*/
- (void)once {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 只执行 1 次的代码(这里面默认是线程安全的)});
}
GCD 快速迭代方法:dispatch_apply
通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。
/*** 快速迭代方法 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函数中
- 参数一:传的时循环次数
- 参数二:传派发队列进去。
- 如果传并发队列,就是并发的执行6次,开多少线程由操作系统决定
- 如果传主队列(当前串行队列),会死锁
- 如果传一个新创建的串行队列,等于同步循环执行,从0-5挨个执行(跟for循环没区别了)
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是 apply—end 一定在最后执行。这是因为 dispatch_apply 方法会等待全部任务执行完毕。
有一个实际的应用场景:处理图像中的像素点时,并行执行:
- (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;
}
GCD 队列组:dispatch_group
用途:我希望等几个任务执行完毕后,再执行最后一个任务时。(控制执行顺序)
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后,再回到主线程执行结束处理。这时候我们可以用到 GCD 的队列组。
使用队列组的 dispatch_group_enter
、dispatch_group_leave
组合来实现 dispatch_group_async
。
- (void)viewDidLoad {[super viewDidLoad];// 创建一个队列组dispatch_group_t group = dispatch_group_create();// 获取用户信息dispatch_group_enter(group);[self fetchUserInfo:^(UserInfo *userInfo) {// 处理用户信息NSLog(@"User info: %@", userInfo);dispatch_group_leave(group);}];// 获取订单列表dispatch_group_enter(group);[self fetchOrderList:^(NSArray *orders) {// 处理订单列表NSLog(@"Orders: %@", orders);dispatch_group_leave(group);}];// 获取优惠券列表dispatch_group_enter(group);[self fetchCouponList:^(NSArray *coupons) {// 处理优惠券列表NSLog(@"Coupons: %@", coupons);dispatch_group_leave(group);}];// 等待所有任务完成dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 所有数据获取完成,进行最终处理NSLog(@"All data fetched!");});
}
调用队列组的 dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中
// 创建一个队列组
dispatch_group_t group = dispatch_group_create();// 获取用户信息
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[fetcher fetchUserInfo:^(UserInfo *userInfo) {// 处理用户信息NSLog(@"User info: %@", userInfo);}];
});// 获取订单列表
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[fetcher fetchOrderList:^(NSArray<Order *> *orders) {// 处理订单列表NSLog(@"Orders: %@", orders);}];
});// 获取优惠券列表
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[fetcher fetchCouponList:^(NSArray<Coupon *> *coupons) {// 处理优惠券列表NSLog(@"Coupons: %@", coupons);}];
});// 等待所有任务完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 所有数据获取完成,进行最终处理NSLog(@"All data fetched!");
});//或者用下面这个:
// 等待所有任务完成
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
if (dispatch_group_wait(group, timeout) == 0) {// 所有数据获取完成,进行最终处理NSLog(@"All data fetched!");
} else {// 超时,可以执行一些错误处理逻辑NSLog(@"Timeout waiting for data");
}或者:
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); //永久阻塞
与 dispatch_group_notify
不同,dispatch_group_wait
是一个同步操作,会阻塞当前线程,直到所有任务完成或者超时。在这里,我们设置了一个 10 秒的超时时间,如果在 10 秒内所有任务都完成,则打印 “All data fetched!”。如果超时,则打印 “Timeout waiting for data”。
这种实现方式更加简单,但可能会导致主线程被阻塞,影响用户体验。如果需要更好的用户体验,可以考虑使用 dispatch_group_notify 的异步方式。
dispatch_group_notify
- 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
dispatch_group_wait
- 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
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_barrier_async/sync栅栏方法
多读单写问题:
多读单写
解决方法一:栅栏方法
解决方法二:读写锁
#import <Foundation/Foundation.h>@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end#import "ZYPerson.h"@interface ZYPerson ()
@endstatic NSString *_name;
static dispatch_queue_t _concurrentQueue;
@implementation ZYPerson
- (instancetype)init
{if (self = [super init]) {_concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT);}return self;
}
- (void)setName:(NSString *)name
{dispatch_barrier_async(_concurrentQueue, ^{_name = [name copy];});
}
- (NSString *)name
{__block NSString *tempName;dispatch_sync(_concurrentQueue, ^{tempName = _name;});return tempName;
}
@end
- (void)viewDidLoad {ZYPerson *obj = [[ZYPerson alloc] init];obj.name = @"aaa";dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{NSString *tmp = [obj name];NSLog(@"1 - %@", tmp);});dispatch_async(concurrentQueue, ^{NSString *tmp = [obj name];NSLog(@"2 - %@", tmp);});dispatch_async(concurrentQueue, ^{NSString *tmp = [obj name];NSLog(@"3 - %@", tmp);});dispatch_barrier_async(concurrentQueue, ^{[obj setName:@"bbb"];NSLog(@"%@", obj.name);});dispatch_async(concurrentQueue, ^{NSString *tmp = [obj name];NSLog(@"4 - %@", tmp);});dispatch_async(concurrentQueue, ^{NSString *tmp = [obj name];NSLog(@"5 - %@", tmp);});dispatch_async(concurrentQueue, ^{NSString *tmp = [obj name];NSLog(@"6 - %@", tmp);});}
这里有一个很关键的理解:读操作的内部实现这里,使用的是dispatch_sync
同步函数,不是异步函数。这里使用同步函数是为了保证读操作的原理性。并不影响读操作可以同时异步进行。读操作时同步还是异步,取决于将set方法是使用同步方法还是使用异步方法,加入到派发队列的。
dispatch_barrier_async/sync 的区别
- dispatch_barrier_async
- (void) barrier {NSLog(@"start");dispatch_queue_t myQueue = dispatch_queue_create("ff", DISPATCH_QUEUE_CONCURRENT);dispatch_async(myQueue, ^{NSLog(@"%@--1", [NSThread currentThread]);});dispatch_async(myQueue, ^{NSLog(@"%@--2", [NSThread currentThread]);});dispatch_barrier_sync(myQueue, ^{NSLog(@"+++barrier ++++++");});NSLog(@"++++++++++++barrier 之后");dispatch_async(myQueue, ^{NSLog(@"%@--3", [NSThread currentThread]);});dispatch_async(myQueue, ^{NSLog(@"%@--4", [NSThread currentThread]);});NSLog(@"last");
}
打印结果:
2020-06-20 08:31:08.892663+0800 OCTestFirst[6439:172483] start
2020-06-20 08:31:08.893001+0800 OCTestFirst[6439:172646] <NSThread: 0x600002a48440>{number = 6, name = (null)}--2
2020-06-20 08:31:08.893007+0800 OCTestFirst[6439:172648] <NSThread: 0x600002a4aa40>{number = 5, name = (null)}--1
2020-06-20 08:31:08.893166+0800 OCTestFirst[6439:172483] +++barrier ++++++
2020-06-20 08:31:08.893274+0800 OCTestFirst[6439:172483] ++++++++++++barrier 之后
2020-06-20 08:31:08.893375+0800 OCTestFirst[6439:172483] last
2020-06-20 08:31:08.893430+0800 OCTestFirst[6439:172646] <NSThread: 0x600002a48440>{number = 6, name = (null)}--4
2020-06-20 08:31:08.893488+0800 OCTestFirst[6439:172648] <NSThread: 0x600002a4aa40>{number = 5, name = (null)}--3
- ** dispatch_barrier_async**
- (void) barrier {NSLog(@"start");dispatch_queue_t myQueue = dispatch_queue_create("ff", DISPATCH_QUEUE_CONCURRENT);dispatch_async(myQueue, ^{NSLog(@"%@--1", [NSThread currentThread]);});dispatch_async(myQueue, ^{NSLog(@"%@--2", [NSThread currentThread]);});dispatch_barrier_async(myQueue, ^{NSLog(@"+++barrier ++++++");});NSLog(@"++++++++++++barrier 之后");dispatch_async(myQueue, ^{NSLog(@"%@--3", [NSThread currentThread]);});dispatch_async(myQueue, ^{NSLog(@"%@--4", [NSThread currentThread]);});NSLog(@"last");
}
打印结果:
2020-06-20 08:34:09.223552+0800 OCTestFirst[6485:174586] start
2020-06-20 08:34:09.223936+0800 OCTestFirst[6485:174586] ++++++++++++barrier 之后
2020-06-20 08:34:09.224071+0800 OCTestFirst[6485:174586] last
2020-06-20 08:34:09.225737+0800 OCTestFirst[6485:175133] <NSThread: 0x60000371bb00>{number = 10, name = (null)}--2
2020-06-20 08:34:09.225876+0800 OCTestFirst[6485:174911] <NSThread: 0x600003702440>{number = 8, name = (null)}--1
2020-06-20 08:34:09.226888+0800 OCTestFirst[6485:175133] +++barrier ++++++
2020-06-20 08:34:09.227540+0800 OCTestFirst[6485:175133] <NSThread: 0x60000371bb00>{number = 10, name = (null)}--3
2020-06-20 08:34:09.227540+0800 OCTestFirst[6485:174911] <NSThread: 0x600003702440>{number = 8, name = (null)}--4
分析以上两个函数:
dispatch_barrier_sync
-
- 会阻塞当前线程,看打印结果可知,“start”一定在dispatch_barrier_sync函数前执行,“end”一定在dispatch_barrier_sync函数后执行
-
- dispatch_barrier_sync 函数添加的block,在当前线程执行
-
- 会将传入dispatch_barrier_sync函数的线程myQueue中的任务,通过dispatch_barrier_sync函数位置把前后分隔开。
** dispatch_barrier_async**
-
- 不会阻塞当前线程,“start”,“end”与dispatch_barrier_async方法是同步执行的,执行顺序不定。
-
- dispatch_barrier_sync函数添加的block,在新开的线程执行
-
- 会将传入dispatch_barrier_async函数的线程myQueue中的任务,通过dispatch_barrier_async函数位置把前后分隔开。
dispatch_suspend / dispatch_resume
dispatch_suspend暂时挂起指定dispatch_queue
dispatch_resume回复执行的dispatch_queue
这些函数对已执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。
GCD线程同步方法总结
我们使用GCD的时候如何让线程同步,也有多种方法
1.dispatch_group
2.dispatch_barrier
3.dispatch_semaphore
面试题
1. 多读单写问题
答:
-
- 栅栏方法(dispatch_barrier_async)
-
- 读写锁(pthread_rwlock)