关于atomic到底安不安全

atomic 实际上相当于一个引用计数器,这个大家很熟悉,如果被标记了atomic,那么被标记了的内存本身就有了一个引用计数器,第一个占用这块内存的线程,会给这个计数器+1,在这个线程操作这块内存期间,其他线程在访问这个内存的时候,如果发现“引用计数器”不为0,则阻塞,实际上阻塞并不等于休眠,他是基于cpu轮询片;休眠除非被叫醒,否则无法继续执行,阻塞则不同,每个cpu 轮询片到这个线程的时候都会尝试继续往下执行

首先我们从最基本的数据类型说起,char int long dobule 比如是这四种,如果在64位系统下,他们分别占1、4、8、8 个字节。想象一下,1个字节也是内存,8个字节也是内存,只要是内存,就有可能产生所谓的资源竞争,也就是多线程并发的问题。据我所知,char int 是绝对线程安全的,也就是说 系统对 char int 类型数据的操作,要么不操作,要么绝对会把这些字节全部操作完,不会并发的问题。

再来说 long double ,对一个long型数据的读写,操作系统有可能分两部分进行,一部分是高位,一部分是低位,所以两个线程同时操作long数据,有可能导致数据不同步,但是据我写的demo测试,这种情况很难出现,因为一个long型的数据最多也就8个字节,对着8个字节都是微妙甚至纳秒级别,一个cpu的轮询片的周期,极大情况下都会比这个时间长,也就说,即使long 不是线程安全的,但是由于本身字节非常少,读写速度极快,快到比cpu轮询的时间片都块的情况下,实际上即使不线程安全,两个线程也不会同时读写这块内存。

但凡事都有例外,demo 不能出现不代表他就是线程安全的,只能说绝大部分情况下,多线程操作一个long 数据问题是不大的。 
继续说double,double 类型的数据同样占8个字节,按道理来说,他读写的速度应当和long 一样快。实际上,也是这样的。但是。一个线程读写内存仅仅只是一个方面,cpu 需要对数据进行计算,这个计算的中间结果一般都会放到寄存器或者cpu 高速缓存,double 数据计算的复杂度远非long型所能比,因此double 数据类型相比long 型数据更容易出现并发的问题。

说到这里其实应该总结一下窍门了,什么样的数据会存在多线程的问题?什么样的数据不会呢?

可以想象一下,如果一个数据占的内存特别大,读写这块数据需要的时间也就越长,如果这个时间长度远远超过线程调度的轮询片,那么就有极大可能出现并发问题。

你就记住,小于等于4个字节的基本类型数据,比如char short int 等等等等,都是线程安全的,只要大于这个规定,都不是线程安全的。。。

ok 我们继续讨论一种特殊的数据类型,指针类型

我们知道,在64位的操作系统下,所有类型的指针,包括void * 都是占用8个字节的。我们上面已经说了,超过4个字节的基本类型数据都会有线程并发的问题。

那所有的指针类型都会有这个问题。

以oc 下的 NSArray * 为例子,如果一个多线程操作这个数据,会有两个层级的并发问题

1、指针本身

2、指针所指向的内存

大家注意下 &array 和 array 的区别 ,其实不用纠结,你就想象现在有两块内存,一块是8字节,一块n字节,8字节里面放的值,就是n字节内存的首地址

ok 现在联系上atomic,如果用@property(atomic)NSArray *array 修饰之后,会有什么影响?网上说的很多,不再赘述,我只想从内存的角度来解释这个过程

首先第一点,你要记住,@property(atomic)NSArray *array 其实修饰的是这个指针,也就是这个8字节内存,跟第二部分数据n字节没有任何关系,被atomic 修饰之后,你不可能随意去多线程操作这个8字节,但是对8字节里面所指向的n字节没有任何限制!这就是所有网络上所说的 atomic 不安全的真相!

自旋锁已被证明不安全,同步锁简单,性能差,nslock 性能略好,dispatch_semphone 性能最好

不加任何保的护场景构建:

现在我们有一个8字节的指针,假如我们做一个初始化 NSArray *array = [[NSArray alloc] init] 这个操作。实际上这个操作有两个意思

1:给8字节赋值

2:开辟了一块n字节的内存区

我们只说这8字节的地址复制,如果没有atomic 修饰,并且假设现在有两个线程正在操作这个指针,一个就是上面的初始化线程,另一个线程就是读这个8字节的指针

首先,假如8字节内部存放的是0x1122334455667788 ok 8字节需要写入这个值,但与此同时,很不巧,另一个读线程现在要读这个8字节里面的值

假如 这个8字节只写了一半的时候 另一个线程来读,那它读到的可能是 0x1122334400000000 OK 实际上,等他读完之后,写线程仍然还未完成

这时候,[[NSArray alloc] init] 的头地址正确的应该是0x1122334455667788 ,而读线程读到的是0x1122334400000000 这时候会出现什么情况?

最好的情况,无非就是个野指针,因为谁也不知道这块地址是否有效或者是否有什么重要的数据,野指针会导致啥不多说了

最坏的情况,这个野地址指向的是重要的一段数据。。。后果可想而知。

所以 atomic 的意义就在于此,在0x1122334455667788 写完之前,读线程是无法读取的,同样的道理,在读线程正在读的过程中,写线程是无法改变8字节的 。atomic 能避免这8字节的值因为多线程的原因被意外破坏,仅此而已。

场景二:

假如现在有atomic 修饰,假如现在有两个线程正在操作这个指针,根据上面的结论,他俩“先后”正确的获取到了内存地址,也就说,他俩都先后、正确的找到了8字节内容所指向的n字节内容,虽然找到这n个字节内容的顺序有先后,但是不影响这两个线程同时去操作这n个字节的数据。

这样问题又来了,两个线程同时去操作n字节内容,如果两个线程都是读线程,一般不会有问题,但是假如至少有一个是写线程,那问题又来了,还是一个读写同步的问题,因此 atomic 虽然规范了 找到这n字节内容的先后顺序,但是它不能规范对着n个字节内容的读写。这就是atomic 的局限性。

如果是指针变量,需要加锁,如果是基本变量,不用考虑,不需要加锁 。

原文链接: 
http://www.cocoachina.com/bbs/read.php?tid=1720812&page=undefined

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值