iOS基础知识总结--atomic为什么不是线程安全
概述:
1、原子操作对线程安全并无任何安全保证。被atomic修饰的属性(不重载设置器和访问器)只保证了对数据读写的完整性,也就是原子性,但是与对象的线程安全无关。
2、线程安全已经有保障情况下、对性能也有要求的情况下可使用nonatomic替代atomic,当然也可以一直使用atomic。
描述:
首先我要纠正一个网上常见的关于atomic非线程安全的举例:如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,有3种可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。所以atomic可并不能保证对象的线程安全。
类似的这个例子相信很多人都见过,看起来也非常合理,没什么错;但细琢磨,这个例子本身没问题,但根本不能证明atomic的非线程安全这个观点!所以面试的时候如果举这个例子!(说明你就没明白atomic的非线程安全性)
首先你得知道什么是线程不安全,线程的不安全是由于多线程访问和修改共享资源而引起的不可预测的结果(有可能crash)。可以简单理解为我们拿到的值是错的。这个例子中,如果线程A getter到的值是个错误的值才能说是线程不安全的,可是这个例子就算线程A可能取到好几种值,你能说取值不对吗;不能。所以这个例子是个错误的例子!
atomic的原子性和nonatomic的非原子性
atomic :系统自动生成的getter/setter方法会进行加锁操作;可以理解过读写锁,可以保证读写安全;较耗时;
nonatomic : 系统自动生成的getter/setter方法不会进行加锁操作;但速度会更快;
下面是两个nonatomic和atomic修饰的变量,我们用代码掩饰其内部实现;
nonatomic和atomic修饰的变量代码实现atomic的原子性和nonatomic的非原子性官方解释
原子的(默认)
--原子是默认设置:如果您不输入任何内容,则您的属性是原子的。
--原子属性可以保证,如果您尝试从中读取内容,则将取回有效值。
--它不能保证该值是多少,但是您将获得合法有效的数据,而不仅仅是垃圾内存(又叫脏数据)。
--这允许您执行的操作是,如果您有多个线程或多个进程指向一个变量,则一个线程可以读取而另一个线程可以写入。
--如果它们同时命中,则保证读取器线程获得两个值之一:更改之前或更改之后。
--原子不会给您带来任何保证,您可能会获得其中哪些值。
--原子通常确实与线程安全混淆,这是不正确的。
--您需要以其他方式保证线程安全。但是,atomic可以保证,如果您尝试读取数据,则肯定会获得某种合法数据。
非原子
--另一方面,您可能会猜到,非原子意味着“不要做原子的事情”。
--您所失去的是保证您总是能得到一些回报。
--如果尝试在写入过程中进行读取,则可能会获取垃圾数据。
--但是,另一方面,您走得更快。
--因为原子属性必须做一些魔术(递归锁)才能保证您将获得一个值,所以它们要慢一些。
--如果这是您经常访问的属性,则可能需要降低为非原子属性,以确保不会造成速度损失。
分析atomic为什么不是线程安全
其实现在一想很奇怪,为什么要把atomic和线程安全联系在一起去探究;atomic只是对属性的getter/setter方法进行了加锁操作,这种安全仅仅是get/set的读写安全,仅此之一,但是线程安全还有除了读写的其他操作,比如:当一个线程正在get/set时,另一个线程同时进行release操作,可能会直接crash。很明显atomic的读写锁不能保证线程安全。 下面两个例子写的就挺好,挺简单:
eg1:如果定义属性NSInteger i是原子的,对i进行i = i + 1操作就是不安全的; 因为原子性只能保证读写安全,而该表达式需要三步操作:
1、读取i的值存入寄存器;
2、将i加1;
3、修改i的值;
如果在第一步完成的时候,i被其他线程修改了,那么表达式执行的结果就与预期的不一样,也就是不安全的
eg2:
结果可能是[10000,20000]之间的某个值,而我们想要的结果是20000;很明显这个例子就会引起线程隐患,而atomic并不能防止这个问题;所以我们说atomic不是线程安全;
扩展:探索nonatomic非线程安全的原因
为什么nonatomic是非线程安全的,我们来看看runtime的源码:
runtime-get runtime-set根据源码,我们可以看到,getter是不会对属性进行retain的,假设当getter执行后,切换到另一个线程,执行setter,setter会对oldValue release,导致oldValue释放。再切回执行getter的线程,getter用到的是已经释放的oldValue。就会发生EXC_BAD_ACCESS的crash。
一般情况下,getter执行后,会在外部对getter获取的属性进行retain,也就是调用objc_retain。但是也许就在getter发生之后,objc_retain之前其他线程执行了setter。这时候,就会导致objc_retain产生EXC_BAD_ACCESS。
那么atomic会不会发生问题呢?根据源码,在获取到属性时,atomic下getter会立即对value进行retain,即使setter对oldValue release了。由于getter已经进行retain,属性不会立即释放。只有使用完成之后才会释放。所以atomic在操作属性的时候可以保证不会crash。
再次来个总结:
nonatomic在线程间切换操作属性的时候容易造成crash,是不安全的。
atomic在线程间切换操作属性可以保证不会crash,也会get到合法的有效数据,但是并不能保证获取的数据就是你的预期值。也是不安全的。
借鉴文章: