当前位置: 首页 > news >正文

「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则会返回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主要作用

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

请添加图片描述

但如果sleep时间变成1,则会出现以下结果:
请添加图片描述

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

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

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

请添加图片描述

信号量调度是公平队列(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、任务2dispatch_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]);
}

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

请添加图片描述

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

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

http://www.dtcms.com/a/298009.html

相关文章:

  • 自然语言处理技术应用领域深度解析:从理论到实践的全面探索
  • Unity 多人游戏框架学习系列十一
  • http-proxy-middleware MaxListenersExceededWarning
  • [2025CVPR-图象分类方向]SPARC:用于视觉语言模型中零样本多标签识别的分数提示和自适应融合
  • 【STM32】FreeRTOS任务的挂起与解挂(四)
  • 学习游戏制作记录(克隆技能)7.25
  • 踩坑记录:因版本不匹配导致 Boost 1.85 编译失败的完整解决过程
  • 二层隧道协议(PPP、PPTP、L2TP)
  • STM32的WI-FI通讯(HAL库)
  • 2025-07-25设置使用权限N次内
  • 《计算机组成原理与汇编语言程序设计》实验报告一 基本数字逻辑及汉字显示
  • OpenGLRender开发记录(二): 阴影(shadowMap,PCF,PCSS)
  • 升级目标API级别到35,以Android15为目标平台(三 View绑定篇)
  • Fluent自动化仿真(TUI命令脚本教程)
  • SQL Server数据库
  • 破局与重构:King’s LIMS 引领电子行业实验室智能化转型
  • 从kHz到GHz:晶振频率范围如何决定其应用场景
  • 打破渠道壁垒:SEO+ASO协同作战实现用户获取量翻倍
  • Spring Cloud Gateway 服务网关
  • Docker 实战大纲
  • HC32 睡眠
  • SpringBoot整合Liquibase提升数据库变更的可控性、安全性、自动化程度(最详细)
  • Claude Code 基于 VUE + KonvaJS 实现海报生成器(附源码)
  • 基于springboot的候鸟监测管理系统
  • 杂谈:前端开发中的常见问题
  • App拉起:唤醒即达,告别繁琐操作
  • C++实战:数据标准化高效实现
  • 自动化测试学习?
  • 零基础学Triton(1) Triton还需要学习吗?
  • Tailwind CSS 自定义工具类与主题配置指南