【iOS多线程(二)】GCD其他方法详解

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主要作用

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁
  1. 异步转同步
  • 众所周知并发队列中的任务,由异步线程执行的顺序是不确定的,两个任务分别由两个线程执行,很难控制哪个任务先执行完,哪个任务后执行完。但有时候确实有这样的需求:两个任务虽然是异步的,但仍需要同步执行。

实际案例:
比如说: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相当于加锁效果

  1. 设置最大开辟的线程数
  • 我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定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. 加锁机制
  • 当你的信号设置为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 两种形式

  1. 基于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位无符号长整形

  1. 基于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函数中

  1. 参数一:传的时循环次数
  2. 参数二:传派发队列进去。
    • 如果传并发队列,就是并发的执行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_enterdispatch_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 的区别

  1. 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
  1. ** 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

    1. 会阻塞当前线程,看打印结果可知,“start”一定在dispatch_barrier_sync函数前执行,“end”一定在dispatch_barrier_sync函数后执行
    1. dispatch_barrier_sync 函数添加的block,在当前线程执行
    1. 会将传入dispatch_barrier_sync函数的线程myQueue中的任务,通过dispatch_barrier_sync函数位置把前后分隔开。

** dispatch_barrier_async**

    1. 不会阻塞当前线程,“start”,“end”与dispatch_barrier_async方法是同步执行的,执行顺序不定。
    1. dispatch_barrier_sync函数添加的block,在新开的线程执行
    1. 会将传入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. 多读单写问题

答:

    1. 栅栏方法(dispatch_barrier_async)
    1. 读写锁(pthread_rwlock)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/393963.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面向 RAG 应用开发者的实用指南和建议

向量搜索并非轻而易举&#xff01; 向量搜索&#xff0c;也称为向量相似性搜索或最近邻搜索&#xff0c;是一种常见于 RAG 应用和信息检索系统中的数据检索技术&#xff0c;用于查找与给定查询向量相似或密切相关的数据。业内通常会宣传该技术在处理大型数据集时非常直观且简单…

【C语言】C语言期末突击/考研--结构体与C++引用

一、结构体--结构体对齐--结构体数组 1.1.结构体的定义、初始化、结构体数组 有时候需要将不同类型的数据组合为一一个整体&#xff0c;以便于引用。 例如&#xff0c;一名学生有学号、姓 名、性别、年龄、地址等属性&#xff0c;如果针对学生的学号、姓名、年龄等都单独定义一…

【MYSQL】表操作

目录 查看当前数据库含有表查看表结构创建表插入&#xff08;新增create&#xff09;查询&#xff08;retrieve&#xff09;全列查询指定列查询查询列是表达式别名查询(as)去重查询(distinct)排序查询(order by)条件查询(where)比较/逻辑运算符使用 分页查询(limit) 一条语句各…

【若依项目-RuoYi】掌握若依前端的基本流程

搞毕设项目&#xff0c;使用前后端分离技术&#xff0c;后端springBoot&#xff0c;前端vue3element plus。自己已经写好前端与后端代码&#xff0c;但想换一个前端界面所以使用到了若依&#xff0c;前前后后遇到许多坑&#xff0c;记录一下&#xff0c;方便之后能够快速回忆。…

尚硅谷谷粒商城项目笔记——八、安装node.js【电脑CPU:AMD】

八、安装node.js 注&#xff1a; [!NOTE] 查看本机系统 官网选择node.js版本 1傻瓜式安装&#xff0c;注意选择路径 图一 图二 至此&#xff0c;nodejs安装完成&#xff01; 2环境配置 找到安装nodejs的路径新增 node_global 和node_cache文件夹 创建完两个空文件夹&#x…

如何快速入门 PyTorch ?

PyTorch是一个机器学习框架&#xff0c;主要依靠深度神经网络&#xff0c;目前已迅速成为机器学习领域中最可靠的框架之一。 PyTorch 的大部分基础代码源于 Ronan Collobert 等人 在 2007 年发起的 Torch7 项目&#xff0c;该项目源于 Yann LeCun 和 Leon Bottou 首创的编程语…

0207、创建场景状态的三个子类

VS使用的是3.5框架&#xff0c;会自带Linq这一行&#xff0c;Unity不支持&#xff0c;需要删除 一、创建三个场景 二、创建三个子类

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——5Webscoket节点的使用

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——5Webscoket节点的使用 ​ 有了前面几篇文章的铺垫&#xff0c;现在已经可以实现我到手测试那一步的 1.解读usb_websocket_display.launch.py ​ 首先进入这个目录/root/dev_ws/src/origincar/originca…

Java语言程序设计——篇十二

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 欢迎大家&#xff1a;这里是我的学习笔记、总结知识的地方&#xff0c;喜欢的话请三连&#xff0c;有问题可以私信&#x1f333;&#x1f333;&…

ChatGPT能代替网络作家吗?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ 当然可以&#xff01;只要你玩写作AI玩得6&#xff0c;甚至可以达到某些大神的水平&#xff01; 看看大神、小白、AI输出内容的区…

【枚举 图论】2242. 节点序列的最大得分

本文涉及知识点 枚举 图论知识汇总 LeetCode 2242. 节点序列的最大得分 给你一个 n 个节点的 无向图 &#xff0c;节点编号为 0 到 n - 1 。 给你一个下标从 0 开始的整数数组 scores &#xff0c;其中 scores[i] 是第 i 个节点的分数。同时给你一个二维整数数组 edges &…

logging日志实操入门

一、代码 import logging from logging.handlers import RotatingFileHandler # 配置日志 log_file_path ./logs/test.log file_handler RotatingFileHandler(log_file_path, maxBytes10, backupCount5)# 设置格式化器&#xff0c;以使日志更易读 formatter logging.Format…

Webstorm的下载与安装

Webstorm的下载 1 在浏览器的地址栏输入https://www.jetbrains.com/webstorm/&#xff0c;进入主页面 2 点击右上角的Download按钮&#xff0c;进入下载页面&#xff0c;如图所示 Webstorm的安装 按步骤逐步安装即可

SwiftUI 如何定制 Picker 视图当前选中行的背景颜色?

功能需求 有时我们希望可以定制 SwiftUI 中 Picker 视图当前选中行的背景色,这可以做到吗? 在上面的演示图中,我们随心所欲地变换着 SwiftUI 中 Picker 视图当前选中行的背景色。这是怎么做到的呢? 在本篇博文中,您将学到以下内容 功能需求1. 钩深极奥:修改 SwiftUI 原…

嵌入式学习之路 13(C语言基础学习——预处理命令)

编程流程 在进行程序开发时&#xff0c;通常遵循编辑源代码、编译、运行和调试这几个主要步骤。 编辑源代码&#xff1a;使用文本编辑器创建或修改程序的源代码&#xff0c;这是整个编程过程的起点。编译&#xff1a;将源代码转换为可执行文件的关键步骤。 预处理&#xff1a…

C#重要知识归纳总结

C#教程 C# 结构体&#xff08;Struct&#xff09; | 菜鸟教程C# 结构体&#xff08;Struct&#xff09; 在 C# 中&#xff0c;结构体&#xff08;struct&#xff09;是一种值类型&#xff08;value type&#xff09;&#xff0c;用于组织和存储相关数据。 在 C# 中&#xff0c…

微服务-实现nacos的集群和Gateway网关的实现、认证校验、解决跨域

1. nacos的集群模式 1.1 分析 nacos在企业中的使用100%都是集群模式。需要掌握nacos集群的搭建 nacos的数据存放在derby本地磁盘中&#xff0c;nacos集群模式会导致数据库数据不一致&#xff0c;使用加一层思想&#xff0c;修改nacos的数据库&#xff0c;使用mysql数据库&…

浅析中国蚁剑的木马加密流量

简介 在蓝帽杯 2022 初赛中&#xff0c;domainhacker 的流量分析题目聚焦于中国蚁剑这款 webshell 管理工具的流量特征。通过对比赛提供的数据包进行解析&#xff0c;本文将深入分析蚁剑在连接木马时产生的加密流量。 公司安全部门&#xff0c;在流量设备中发现了疑似黑客入侵的…

idea使用free流程,2024idea免费使用

1.先到官网下载&#xff0c;这里选择win系统的&#xff0c;点击下图的.exe https://www.jetbrains.com/idea/download/?sectionwindows 2.下载好后基本上就是一直点击“下一步”到直到安装好&#xff0c;安装好后先打开软件后关闭退出 3.下载配配套资料 链接: https://pan.ba…

MySQL内存模型与相关技术

MySQL实例的大概结构如下 如上图所示&#xff0c;InnoDB的存储引擎右多个内存块 维护所有进程/线程需要访问多个内部数据结构缓存磁盘上的数据&#xff0c;方便快速读取&#xff0c;且修改的数据缓存在此&#xff0c;满了后统一写入磁盘重做日志&#xff08;redo log&#xf…