> 技术文档 > 「iOS」——GCD其他方法详解

「iOS」——GCD其他方法详解


GCD学习

  • GCD其他方法
      • dispatch_semaphore (信号量
      • **什么是信号量**
      • dispatch_semaphore主要作用
        • dispatch_semaphore主要作用
          • 异步转同步
          • 设置一个最大开辟的线程
          • 加锁机制
        • dispatch_time_t 两种形式
        • GCD一次性代码(只执行一次)
      • dispatch_barrier_async/sync栅栏方法

GCD其他方法

前面对GCD进行了简单的学习,这里笔者对一些容易遗忘和重要的方法再次学习。

dispatch_semaphore (信号量)

什么是信号量

引用学长的解释

以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看 门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开 车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。

信号量主要用作同步锁,用于控制GCD最大并发数

主要涉及以下三个函数:

// 创建信号量,value:初始信号量数 如果小于0则会返回NULLdispatch_semaphore_create(long value); // 发送信号量是的信号量+1dispatch_semaphore_signal(dispatch_semaphore_t deem);//可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 

注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

dispatch_semaphore主要作用

dispatch_semaphore主要作用
  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁
异步转同步
- (void)cjl_testSemaphore1{ 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\"); //信号量+1 dispatch_semaphore_signal(semaphore); }); 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\"); //信号量+1,相当于解锁 dispatch_semaphore_signal(semaphore); }); }

多次运行的结果都是A,B,C顺序执行,让A,B,C异步执行变成同步执行,dispatch_semaphore相当于加锁效果

设置一个最大开辟的线程数
- (void)cjl_testSemaphore{//设置最大开辟的线程数为3 dispatch_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(2); NSLog(@\"完成当前任务 %ld\", i); //释放信号 dispatch_semaphore_signal(semaphore); }); }}

「iOS」——GCD其他方法详解

但如果sleep时间变成1,则会出现以下结果:
「iOS」——GCD其他方法详解

这是因为出现了优先级反转的问题。

先介绍什么是优先级反转。

在程序执行时多个任务可能会对同—份数据产生 竞争’因此任务会使用锁来保护共享数据°假设现在有3个任务A、B`C’它们的优先 级为A>B>C任务C在运行时持有一把锁’然后它被高优先级的任务A抢占了(任 务C的锁没有被释放)。此时任务A恰巧也想申请任务C持有的锁’但是申请失败,因 此进人阻塞状态等待任务C放锁。此时’任务B、C都处于可以运行的状态,由于任务 B的优先级高于C,因此B优先运行°综合观察该情况’就会发现任务B好像优先级高 于任务A’先于任务A执行。

「iOS」——GCD其他方法详解

信号量调度是公平队列(FIFO/不考虑 QoS)+ 系统线程调度是根据优先级(QoS)来的,两者机制不一致,所以可能造成高优先级线程因为信号量资源被低优先级线程占用而“被阻塞”。

加锁机制
  • 当你的信号设置为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_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位无符号长整形
GCD一次性代码(只执行一次)

使用dispatch_once方法能保证某段代码在程序运行过程中只执被执行1次,即使在多线程的环境下,该方法也可以保证建成安全。之前在创建单例模式时就接触过该方法。

- (void)once {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ // 只执行 1 次的代码(这里面默认是线程安全的) });}

dispatch_barrier_async/sync栅栏方法

在访问数据库或者文件的时候,我们可以使用Serial Dispatch Queue可避免数据竞争问题。即多读单写

- (void)cjl_testBarrier{ /* dispatch_barrier_sync & dispatch_barrier_async 应用场景:同步锁 等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。 简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务 - dispatch_barrier_async:前面的任务执行完毕才会来到这里 - dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行 - dispatch_barrier_async可以控制队列中任务的执行顺序, - 而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用) */ [self cjl_testBarrier1]; [self cjl_testBarrier2];}- (void)cjl_testBarrier1{ //串行队列使用栅栏函数 dispatch_queue_t queue = dispatch_queue_create(\"CJL\", DISPATCH_QUEUE_SERIAL); NSLog(@\"开始 - %@\", [NSThread currentThread]); dispatch_async(queue, ^{ sleep(2); NSLog(@\"延迟2s的任务1 - %@\", [NSThread currentThread]); }); NSLog(@\"第一次结束 - %@\", [NSThread currentThread]); //栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1、任务2 dispatch_barrier_async(queue, ^{ NSLog(@\"------------栅栏任务------------%@\", [NSThread currentThread]); }); NSLog(@\"栅栏结束 - %@\", [NSThread currentThread]); dispatch_async(queue, ^{ sleep(2); NSLog(@\"延迟2s的任务2 - %@\", [NSThread currentThread]); }); NSLog(@\"第二次结束 - %@\", [NSThread currentThread]);}- (void)cjl_testBarrier2{ //并发队列使用栅栏函数 dispatch_queue_t queue = dispatch_queue_create(\"CJL\", DISPATCH_QUEUE_CONCURRENT); NSLog(@\"开始 - %@\", [NSThread currentThread]); dispatch_async(queue, ^{ sleep(2); NSLog(@\"延迟2s的任务1 - %@\", [NSThread currentThread]); }); NSLog(@\"第一次结束 - %@\", [NSThread currentThread]); //由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序 dispatch_barrier_async(queue, ^{ NSLog(@\"------------栅栏任务------------%@\", [NSThread currentThread]); }); NSLog(@\"栅栏结束 - %@\", [NSThread currentThread]); dispatch_async(queue, ^{ sleep(2); NSLog(@\"延迟2s的任务2 - %@\", [NSThread currentThread]); }); NSLog(@\"第二次结束 - %@\", [NSThread currentThread]);}

重点观察并发队列中使用栅栏函数。会在栅栏前的任务完成后,才执行栅栏后的任务。

「iOS」——GCD其他方法详解

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函数位置把前后分隔开。

这也是为什么栅栏结束会出现在栅栏任务之前。