不再安全的 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 @ ...
随机推荐
- js中callback执行
<!DOCTYPE HTML> <html> <head> <meta charset="GBK" /> <title> ...
- [codeforces 1037D] Valid BFS? 解题报告(验证bfs序,思维题)
题目链接:http://codeforces.com/problemset/problem/1037/D 题目大意: 给出一棵树,询问一个序列是否可能为这棵树从节点1开始遍历的bfs序 题解: 对于每 ...
- ubuntu18.04中安装iNode
title: ubuntu18.04中安装iNode toc: false date: 2018-09-01 17:52:20 categories: methods tags: ubuntu iNo ...
- js小知识 双叹号(!!)
!!:一般用来将后面的表达式强制转换为布尔值(boolean):true或者false; avascript约定规则为: false.undefinded.null.0.”” 为 false tr ...
- jQuery学习(一)——jQuery入门
1.jQuery基础 Jquery它是一个库(框架),要想使用它,必须先引入! jquery-1.8.3.js:一般用于学习阶段. jquery-1.8.3.min.js:用于项目使用阶段 官网下载后 ...
- Pyinstaller 1 使用PyInstaller
使用PyInstaller pyinstaller命令的语法是: pyinstaller[ options ] script [ script ...] | spec文件 在最简单的情况下,将当前目录 ...
- java redistemplate
//添加一个 key ValueOperations<String, Object> value = redisTemplate.opsForValue(); value.set(&quo ...
- 【转载】spring-boot 项目跳转到JSP页面
原路径:https://blog.csdn.net/qq_36820717/article/details/80008225 1.新建spring-boot项目 目录结构如下 2.新建TestCon ...
- Linux系统下安装配置 OpenLDAP + phpLDAPadmin
实验环境: 操作系统:Centos 7.4 服务器ip:192.168.3.41 运行用户:root 网络环境:Internet LDAP(轻量级目录访问协议)是一个能实现提供被称为目录服务的信息服务 ...
- MySQL中将数据库表名修改成大写的存储过程
原文:MySQL中将数据库表名修改成大写的存储过程 MySQL中将数据库表名修改成大写的存储过程 创建存储过程的代码: DROP PROCEDURE IF EXISTS uppercaseTablen ...