文章目录

  • 使用场景
  • 原理
    • 关于dispatch_compiler_barrier
  • 性能测试

使用场景

dispatch_once 能够保证代码块只执行一次,即使在多线程使用时。

一般来说我们如果要保证代码只执行一次,就是进行加锁,通过修改一个变量值0 -> 1 来判断这段代码是否执行过。

在iOS中dispatch_once经常被用来创造单例对象,或者进行方法交换swizzle method

例如 CCManager 继承自NSObject:

+ (instancetype)shared {static CCManager *manager = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{manager = [[super allocWithZone:NULL] init];});return manager;
}

这里有几个疑问:

  1. 为什么onceToken 需要用 static 修饰?
  2. dispatch_once 做了什么?

原因如下:

  1. static是为了保证onceToken唯一,不然你想想,你每次创建一个onceToken = 0 ,然后传入,内部根据 onceToken = 0的话就执行block,那么不就乱套了?
  2. dispatch_once 其实就是将onceToken 值进行修改,其默认是0执行完后变为-1,然后每次进行判断这个值,为-1 就返回,同时做了一些编译期的优化

原理

typedef intptr_t dispatch_once_t;

intptr_t 其实就是一个long ,所以dispatch_once_t 就是一个long

那么我们打印其值

+ (instancetype)shared {static CCManager *manager = nil;static dispatch_once_t onceToken;NSLog(@"A %ld",onceToken);dispatch_once(&onceToken, ^{manager = [[super allocWithZone:NULL] init];NSLog(@"B %ld",onceToken);});NSLog(@"C %ld",onceToken);return manager;
}
2020-04-07 19:58:06.993385+0800 DispatchOnceTest[50656:1743273] A 0
2020-04-07 19:58:06.993597+0800 DispatchOnceTest[50656:1743273] B 768
2020-04-07 19:58:06.993822+0800 DispatchOnceTest[50656:1743273] C -1

这样子就明白其过程了,0 -> 768(并不固定) -> -1

源码中是这样的

DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
DISPATCH_SWIFT3_UNAVAILABLE("Use lazily initialized globals instead")
void
_dispatch_once(dispatch_once_t *predicate,DISPATCH_NOESCAPE dispatch_block_t block)
{if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {dispatch_once(predicate, block);} else {dispatch_compiler_barrier();}DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}

DISPATCH_EXPECT 告诉编译器,这个值大概率为-1~0l,告诉编译器优先读取下面的指令达到优化的效果。(因为初始化一次后,后面都是直接返回了)

这里~0l 是将一个长整型(long)0按位取反。long一般占4字节对于16进制表示则为0xFFFFFFFF

0x0000000 16进制2位表示一个字节 0x00,转为二进制0000 0000然后取反1111 1111 即0xFF, 0x00 -> 0xFF

关于dispatch_compiler_barrier

#define dispatch_compiler_barrier()  __asm__ __volatile__("" ::: "memory")

__asm__ __volatile__("" ::: "memory")其实就是个空嵌入式汇编语句,其中"memory"表示,强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registerscache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存

如果你想了解其实现过程,可以看这篇文章 深入浅出 GCD 之 dispatch_once
文中讲解了: dispatch_once 是如何保证线程安全的,以及使用信号量
机制等等。

性能测试

那么了解了原理,我们测试各种锁和dispatch_once的性能

extern uint64_t dispatch_benchmark(size_t count, void (^block)(void));// pthread_mutex_lock
void dispatch_once_pthread(dispatch_once_t *predicate, dispatch_block_t block) {static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex);if(!*predicate) {block();*predicate = 1;}pthread_mutex_unlock(&mutex);
}// spinlock
void dispatch_once_spinlock(dispatch_once_t *predicate, dispatch_block_t block) {static OSSpinLock lock = OS_SPINLOCK_INIT;OSSpinLockLock(&lock);if(!*predicate) {block();*predicate = 1;}OSSpinLockUnlock(&lock);
}

执行代码为

 size_t count = 1000000;// pthread_mutex_lockuint64_t time1 = dispatch_benchmark(count, ^{static dispatch_once_t onceToken;dispatch_once_pthread(&onceToken, ^{ });});NSLog(@"dispatch_once_pthread = %llu ns", time1);// spinlockuint64_t time2 = dispatch_benchmark(count, ^{static dispatch_once_t onceToken;dispatch_once_spinlock(&onceToken, ^{ });});NSLog(@"dispatch_once_spinlock = %llu ns", time2);// dispatch_onceuint64_t time3 = dispatch_benchmark(count, ^{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ });});NSLog(@"dispatch_once = %llu ns", time3);

结果:

2020-04-07 20:48:32.944770+0800 DispatchOnceTest[51346:1777555] dispatch_once_pthread = 101 ns
2020-04-07 20:48:33.019640+0800 DispatchOnceTest[51346:1777555] dispatch_once_spinlock = 70 ns
2020-04-07 20:48:33.048097+0800 DispatchOnceTest[51346:1777555] dispatch_once = 25 ns

最后附上demo下载地址

链接:https://pan.baidu.com/s/1_Zar_4T1X0-6SV0iHZ04Bg 密码:pd96

对于 OSSpinlock 不再安全
由于iOS线程中的优先级翻转,当低优先级线程被锁住访问共享资源时,高优先级线程如果访问访问共享资源,由于高优先级线程始终会在低优先级线程前执行,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。 见 博文地址

iOS开发-dispatch_once相关相关推荐

  1. iOS开发 - 多线程相关的概念

    1.进程 概念 在系统中正在运行的程序 特点 进程之间相互独立,每个进程运行在自己的内存空间内 实例 同时打开QQ.迅雷,系统会启动两个不同的进程 2.线程 概念 线城是进程的基本执行单元,即进程想要 ...

  2. iOS开发- ios学习资源(持续更新)

    mark一些自己在学习过程中收集的资源.免得需要的时候没地方找. 持续更新.(最新更新时间: 2014.4.4) 1.苹果官方文档 构建iOS程序:下面的这篇文章介绍了 iOS 程序开发的过程: St ...

  3. Mac和ios开发资源汇总

    目录 1.苹果官方文档 2.邮件列表 3.论坛 4.网站 5.博客 6.大会 7.播客和录像 正文 1.苹果官方文档 构建iOS程序:下面的这篇文章介绍了 iOS 程序开发的过程: Start Dev ...

  4. Mac和iOS开发资源汇总—更新于2013-10-14

    From:http://beyondvincent.com/blog/2013/07/18/106/ 1U55JG9-0 小引 本文主要汇集一些苹果开发的资源,会经常更新,建议大家把这篇文章单独收藏( ...

  5. 学习iOS开发的第1天

    大学生活结束,来到深圳工作,准备从事ios开发.今天开始上班了,从0开始学习ios.每天苦逼地学习,争取快速掌握ios开发技术.从此每天写一篇博客,记录每天学到的知识. 今天第一次工作,启动mac m ...

  6. 【转】 iOS开发之打包上传到App Store——(一)各种证书的理解

    OK,有日子没写iOS开发的相关文章啦,主要是最近的精力都没在这上面,不过既然产品已经快要出来了,就有必要了解一下各种证书啥的(众所周知iOS的一堆证书可是很让人头大呀),最近确实被这个搞得头大,然后 ...

  7. iOS开发- ios学习资源

    1.苹果官方文档 构建iOS程序:下面的这篇文章介绍了 iOS 程序开发的过程: Start Developing iOS Apps Today 构建Mac OS X程序:下面这篇文章介绍了Mac O ...

  8. iOS开发中使用UILabel设置字体的相关技巧小结

    这篇文章主要介绍了iOS开发中UILabel设置字体的相关技巧小结,代码基于传统的Objective-C,需要的朋友可以参考下 一.初始化 复制代码代码如下: UILabel *myLabel = [ ...

  9. iOS开发相关书籍推荐

    前言:开发书籍书单,找相关书籍时发现的书单,本文只当书单参考,不提供资源下载. 一.网络层 1. <HTTP权威指南(中文版)> 2. <IOS网络高级编程> 二.架构.程序设 ...

  10. iOS开发相关图书推荐

    Objective-C编程之道:iOS设计模式解析 作      者 [美] Carlo Chung 著:刘威 译 出 版 社 人民邮电出版社 出版时间 2011-11-01 版      次 1 页 ...

最新文章

  1. Download the Gantt Chart Template
  2. 如何隐藏android的屏幕上的Title Bar
  3. Hbase的读写速度,写比读快
  4. Linux常用的网络命令笔记
  5. IE9下apply的使用方式
  6. oracle删除多条从js到java_一次oracle大量数据删除经历
  7. 2018怎么打开2019_2019 年,我还是没有摆脱 Micro USB
  8. 软件工程的难题-解耦问题
  9. 如何从PDF文件中快速的提取PDF文件
  10. 套接字超时选项(SO_RCVTIMEO 与 SO_SNDTIMEO)
  11. 为什么我只贴代码不给你们源码?
  12. 拓端tecdat|Python、R对小说进行文本挖掘和层次聚类可视化分析案例
  13. 深港澳金融科技师(SHMFTTP)一级考试
  14. ArcGIS按属性选择多个地类
  15. 【编码实现】结合encoder和projector对w进行优化(projector_encoder.py)
  16. 构造函数,静态关键字,静态代码块,构造代码块,局部代码块
  17. FingerGestures手势插件的使用
  18. MSYS 1.0.11 + MinGW安装方法
  19. 上海程序员 落户攻略
  20. Json格式的数据集标签转化为有效的txt格式(data_coco)

热门文章

  1. Java 获取屏幕尺寸
  2. 计算机网络应用云计算,计算机网络云计算技术应用
  3. 李智慧 - 架构师训练营 第六周
  4. 【面试题】单链表的操作1
  5. 蒙特卡洛与遗传算法介绍
  6. 2014年5月份第1周51Aspx源码发布详情
  7. java+selenium——Navigate命令
  8. 【EDM邮件营销】独立站卖家如何通过用户标签提高EDM邮件打开率
  9. 晶圆划片如何提高切割品质?陆芯半导体告诉你
  10. Leetcode-机器人大冒险 (python)