最近在写线程安全方面的总结,顺便也对原子操作复习了一波。有错误之处请各位朋友指点指点,在此谢谢。
介绍一下原子操作:
1、定义:
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch。
如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构,那么这个操作是一个原子(atomic)操作。
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。
将整个操作视作一个整体是原子性的核心特征。
2、实现:
原子性不可能由软件单独保证--必须需要硬件的支持,因此是和架构相关的。
在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
property中的atomic和nonatomic的选择:
官方文档对于atomic的描述:
This means that the synthesized accessors ensure that a value is always fully retrieved by the getter method or fully set via the setter method, even if the accessors are called simultaneously from different threads.
Because the internal implementation and synchronization of atomic accessor methods is private, it’s not possible to combine a synthesized accessor with an accessor method that you implement yourself. You’ll get a compiler warning if you try, for example, to provide a custom setter for an atomic, readwrite property but leave the compiler to synthesize the getter.
尝试为标志为atomic的属性重载setter方法得到警告如下:
Writable atomic property 'name' cannot pair a synthesized getter with a user defined setter
根据官方文档的提示,我猜测如果标志为atomic的属性被重载setter or getter时,因其内置的原子操作的实现是私有的,而我们重载出的setter和getter在调用时被优先寻址得到,且因并无实现原子操作(就算有也与系统内置有所差异)所以要求重新synthesize该属性,如果没有自实现原子操作,那么该setter或者getter接口就没有了该性质,那么该属性中atomic的mark也就毫无意义,这是被重载的情况。
如果属性未被重载,如文档所说在设置器和访问器的使用上是保持原子操作的,也就是能通过getter和setter完整地设置一个值(也就是原子操作)。这里的完整地设置一个值我的理解是不受其他指令的影响。但是实际的开发中,我们更加注重的是数据在使用中线程安全的问题,而非这个单纯的数据读写时的保持完整性。在线程安全得到保证时,因为资源的使用受到约束,属性上标志为atomic还是nonatomic已然无关紧要。而效率上而言,nonatomic缺少了对于总线的约束因此其速率比atomic较高。
同时也看了MrPeak的结论:
读写(load or store)的内存长度小于等于地址总线的长度,那么读写的操作是原子的,一次完成。比如bool,int,long在64位系统下的单次读写都是原子操作。
个人感觉其实也算不上是原子操作。看下原子操作的实现本质是CPU在若干条机器指令执行期对总线进行加锁,防止别的CPU通过总线对内存进行访问。在对变量的赋值操作过程,汇编下来会有读值到寄存器,寄存器赋值于内存中等若干指令,如果这些指令步骤是非连贯或者是受到多线程干扰(这是完全有可能的),那么说是一个原子操作也就不成立了。
另外苹果的编程指南中也提到:
Note: Property atomicity is not synonymous with an object’s thread safety.
属性的原子性与对象的线程安全无关。
描述一下线程安全问题:
重载的设置器与普通接口一样,因为代码块存在多个逻辑,都会受到线程干扰。
可以加个最简单也是最耗时的锁synchronized(确保ID唯一)使作用域内数据加锁,便保证的数据逻辑上的正确。
另外其实在我预想中会有这么一种情况:
-[Atest setAge:] : 0
-[Atest setAge1:] :1
-[Atest setAge2:] :2
会打印上面这么几个结果,因为从多线程角度而言这几个情况也是有可能发生的,但是在运行过程,我加大了运算规模依然未找到这个情况,目前猜测是我的规模未够大,或者是编译器做了某一个优化导致这种情况并未发生。
总结:
1、原子操作对线程安全并无任何安全保证。被atomic修饰的属性(不重载设置器和访问器)只保证了对数据读写的完整性,也就是原子性,但是与对象的线程安全无关。
2、线程安全有保障、对性能有要求的情况下可使用nonatomic替代atomic,当然也可以一直使用atomic。
参考:
1、百度百科
2、官方文档
3、iOS多线程到底不安全在哪里?
4、原子操作 vs 非原子操作
5、stackoverflow