不再安全的 OSSpinLock
自旋锁的本质是持续占有cpu,直到获取到资源。与其他锁的忙等待的实现机制不同。
昨天有位开发者在 Github 上给我提了一个 issue,里面指出 OSSpinLock 在新版 iOS 中已经不能再保证安全了,并提供了几个相关资料的链接。我仔细查了一下相关资料,确认了这个让人不爽的 bug。
OSSpinLock 的问题
2015-12-14 那天,swift-dev邮件列表里有人在讨论 weak 属性的线程安全问题,其中有几位苹果工程师透露了自旋锁的 bug,对话内容大致如下:
新版 iOS 中,系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。
具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。
苹果工程师 Greg Parker 提到,对于这个问题,一种解决方案是用 truly unbounded backoff 算法,这能避免 livelock 问题,但如果系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另一种方案是使用 handoff lock 算法,这也是 libobjc 目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。
libobjc 里用的是 Mach 内核的 thread_switch() 然后传递了一个 mach thread port 来避免优先级反转,另外它还用了一个私有的参数选项,所以开发者无法自己实现这个锁。另一方面,由于二进制兼容问题,OSSpinLock 也不能有改动。
最终的结论就是,除非开发者能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中所有类型的自旋锁都不能再使用了。
OSSpinLock 的替代方案
为了找到一个替代方案,我做了一个简单的性能测试,对比了一下几种能够替代 OSSpinLock 锁的性能。测试是在 iPhone6、iOS9 上跑的,代码在这里。我尝试了不同的循环次数,结果并不都一样,我猜这可能是与 CPU Cache 有关,所以这个结果只能当作一个定性分析。

可以看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的。有消息称,苹果在新系统中已经优化了 pthread_mutex 的性能,所以它看上去和 OSSpinLock 差距并没有那么大了。
开源社区的反应
苹果
查看 CoreFoundation 的源码能够发现,苹果至少在 2014 年就发现了这个问题,并把CoreFoundation 中的 spinlock 替换成了 pthread_mutex,具体变化可以查看这两个文件:CFInternal.h(855.17)、CFInternal.h(1151.16)。苹果自己发现问题后,并没有更新 OSSpinLock 的文档,也没有告知开发者,这有些让人失望。
google/protobuf 内部的 spinlock 被全部替换为 dispatch_semaphore,详情可以看这个提交:https://github.com/google/protobuf/pull/1060。用 dispatch_semaphore 而不用 pthread_mutex 应该是出于性能考虑。
其他项目
因为 OSSpinLock 出现这种问题的几率很小,也没有引起很大的重视,我所能找到的也只有 ReactiveCocoa在讨论这个问题。
相关链接
https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000344.html
http://mjtsai.com/blog/2015/12/16/osspinlock-is-unsafe/
http://engineering.postmates.com/Spinlocks-Considered-Harmful-On-iOS/
https://twitter.com/steipete/status/676851647042203648
不再安全的 OSSpinLock的更多相关文章
- 深入理解 iOS 开发中的锁
来源:伯乐在线 - 夏天然后 链接:http://ios.jobbole.com/89474/ 点击 → 申请加入伯乐在线专栏作者 摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大 ...
- iOS中保证线程安全的几种方式与性能对比
来源:景铭巴巴 链接:http://www.jianshu.com/p/938d68ed832c 一.前言 前段时间看了几个开源项目,发现他们保持线程同步的方式各不相同,有@synchronized. ...
- iOS 多线程之线程锁Swift-Demo示例总结
线程锁是什么 在前面的文章中总结过多线程,总结了多线程之后,线程锁也是必须要好好总结的东西,这篇文章构思的时候可能写的东西得许多,只能挤时间一点点的慢慢的总结了,知道了线程之后要了解线程锁就得先了解一 ...
- Aspects 源码学习
AOP 面向切面编程,在对于埋点.日志记录等操作来说是一个很好的解决方案.而 Aspects 是一个对于AOP编程的一个优雅的实现,也可以直接借助这个库来使用AOP思想.需要值得注意的是,Aspect ...
- iOS开发之用到的几种锁整理
1. iOS中的互斥锁 在编程中,引入对象互斥锁的概念,来保证共享数据操作的完整性.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象. 1.1 @sync ...
- iOS 中的各种锁
在日常开发过程中,为了提升程序运行效率,以及用户体验,我们经常使用多线程.在使用多线程的过程中,难免会遇到资源竞争问题.我们采用锁的机制来确保线程安全. 线程安全 当一个线程访问数据的时候,其他的线程 ...
- OSSpinLockLock加锁机制,保证线程安全并且性能高
在aspect_add.aspect_remove方法里面用了aspect_performLocked, 而aspect_performLocked方法用了OSSpinLockLock加锁机制,保证线 ...
- iOS AOP框架Aspects实现原理
总结: Aspects 是对 类的继承结构isa.mataclass结构的调整和维护:相当于链表的节点插入和删除: 同时使用method Swizzling 对方法统一重定向: 同时使用类似代理的机制 ...
- Atomic原子操作原理剖析
前言 绝大部分 Objective-C 程序员使用属性时,都不太关注一个特殊的修饰前缀,一般都无脑的使用其非默认缺省的状态,他就是 atomic. @interface PropertyClass @ ...
随机推荐
- gson的安装和使用
gson的安装和使用 1.安装 2.布局 <?xml version="1.0" encoding="utf-8"?> <LinearLayo ...
- mysql实战45讲读书笔记(二) 一条SQL更新语句是如何执行的 极客时间
前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块.相信你还记得,一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 那么,一条更新语 ...
- BZOJ 2190 欧拉函数
思路: 递推出来欧拉函数 搞个前缀和 sum[n-1]*2+3就是答案 假设仪仗队是从零开始的 视线能看见的地方就是gcd(x,y)=1的地方 倒过来一样 刨掉(1,1) 就是ans*2+1 再加一下 ...
- layui(弹出层)
首先引入文件 layui.css jquery.min.js layui.js 弹出层 data-method 后面的属性控制是什么弹窗,在js中写方法 <div class="sit ...
- 最长公共子序列(稀疏序列)nlogn解法
首先这种做法只能针对稀疏序列, 比如这种情况: abc abacabc. 会输出5 ,,,,就比较尴尬, #include<iostream> #include<cstdio> ...
- windows2008搭建ftp服务器
控制面板 操作—〉添加角色 (web,文件服务) windows 搜索中: 添加ftp站点: 计算机---管理 –服务器管理 --- 添加一个用户 ,密码. 防护墙高级设置中添加 出入站规则 允许2 ...
- ajax返回数据时,如何将javascript值(通常为对象或数组)转为json字符串
ajax获取值时,返回的数据为空时 alert后出现 [ ]; 用if语句判断时不为空,此时如何判断返回的数据是否为空.可将返回的值转化为json字符串. JSON.stringify() 方法用于将 ...
- Volatile variables
Volatile variables apply another type of memory constraint to individual variables. The compiler oft ...
- Oracle日期的加减运算
无论是DATE还是timestamp都可以进行加减操作.可以对当前日期加年.月.日.时.分.秒,操作不同的时间类型,有三种方法: 1 使用内置函数numtodsinterval增加小时,分钟和秒2 加 ...
- Myeclipse学习总结(7)——Eclipse插件之Maven配置及问题解析
前言: 今天在自己环境装了Maven环境, 并且安装了Eclipse插件, 在查找插件过程中确实遇到一些问题, 好不容易找到一个 却又有问题. 装好了插件之后, 用Eclipse创建Maven项目却 ...