2018-07-18关于atomic为何不安全?

整理自论坛一位大神的回复,非原创

首先我表明自己的立场先,实际上atomic 是安全的。而且是绝对安全的 

atomic 实际上就是原子操作,这个概念其实并不新鲜,早在linux系统下编程本身也是有这个东西的,所谓原子,就是不可再化分,已经是最小的操作单位(所谓操作指的是对内存的读写)网上很多地方都在讨论oc下的atomic 不安全,不能保证数据的并发性,实际上有一点误导了大家,认为atomic 本身是不安全的

实际上,并非atomic 不安全,而是网上一些说法有问题,下面就来剖析一下 atomic 是如何在有效范围内安全的 

所谓一个数据的线程安全,简单点来说就是这块数据即使有多个线程同时读写,也不会出现数据的错乱,内存的最后状态总是可以预见的,如果这块内存的数据被一个多线程读写之后,出现的结果是不可预见的,那么就可以说这块内存是“线程不安全的”

其实这个状态很容易理解,同一个箱子,有的人在里面放球,有的人从里面拿,如果没有一个有规则的顺序,都乱哄哄的一起进行,那么最后箱子里几个球只能靠猜了,所以atomic 就是解决“线程操作同一块内存顺序”的一个方案

atomic 实际上相当于一个引用计数器,这个大家很熟悉,如果被标记了atomic,那么被标记了的内存本身就有了一个引用计数器,第一个占用这块内存的线程,会给这个计数器+1,在这个线程操作这块内存期间,其他线程在访问这个内存的时候,如果发现“引用计数器”不为0,则阻塞,实际上阻塞并不等于休眠,他是基于cpu轮询片,休眠除非被叫醒,否则无法继续执行,阻塞则不同,每个cpu 轮询片到这个线程的时候都会尝试继续往下执行,可见 阻塞相对于休眠来讲,阻塞是主动的,休眠是被动的,如果引用计数器为0,轮询片到来,则先给这块内存的引用计数器+1,然后再去操作,atomic 实现操作序列化的具体过程大概就是如此,说来很容易理解,但是为什么还会有歧义? 

首先我们从最基本的数据类型说起,char int long dobule 比如是这四种,如果在64位系统下,他们分别占1、4、8、8 个字节。想象一下,1个字节也是内存,8个字节也是内存,只要是内存,就有可能产生所谓的资源竞争,也就是多线程并发的问题。据我所知,char int 是绝对线程安全的,也就是说 系统对 char int 类型数据的操作,要么不操作,要么绝对会把这些字节全部操作完,不会并发的问题,也就不会产生所谓所线程访问的问题。也就说,即使是一个并发的多线程,对一个char 或者int 字节进行操作,无论怎么操作,无论情况如何极端,都不会导致数据的错乱,可以这么说:char int 是绝对线程安全的 。再来说 long double 这两个并没有char int 那般的特殊待遇,实际上,这两个分别占了8个字节,对一个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、指针所指向的内存 

上面已经说了,指针本身也是占用内存的,并且一定是8个字节,第二部分,指针所指向的内存,这个占多少字节就不一定了,有可能非常大,有可能也就1个字节

所以我们考虑NSArray * array  这个数据array 多线程操作的时候,必须分成两部分来描述,一个是&array这个指针本身,另一个则是它所指向的内存 array

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

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

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

我们来看一下,这能怪atomic? 本身你修饰的是一个指针,并且atomic 已经完美的履行了它的指责,你现在不可能对这个8字节进行无序的多线程操作,这就够了呀!atomic没有任何鸟问题。有问题的是人,你本身并未对n字节做任何的限制,所以把问题怪罪到atomic 上真的是很不合理。

另外我们回忆一下网络的说法,说atomic 只对 get 和 set 方法起作用,这个说法很容易理解

我们知道,这个8字节里面存储的数据,是n字节数据的头地址,如果更改8字节数据的内容,那么最后通过这个指针访问到的数据就会完全不一样,这个可以理解吧?8字节相当于楼管,里面的数据相当于整栋楼的钥匙,给你不同的钥匙,你是不是就进的是不同的房间?

通过atomic 我们可以保证这个“指针”被有序的访问,也仅仅只能保证到这。

现在我们有一个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 的局限性

这就跟推塔一个道理,你站在塔内,别人进来肯定被打,你跑出去塔的攻击范围,被人搞,然后抱怨塔不起作用,这貌似真的很搞笑 

------内存内容安全问题

@synchronized 

如果代码执行时间是1ms 加上@sy 之后是2ms  这就是严重影响性能

但是对你主观的影响,1ms 和2ms 貌似没什么感觉 

目前最推荐实用的就是 dispatch_semphone   但是这个用起来代码量比较大, @syxxxxx  这个性能虽然不咋地,但是就多2行代码,实际上最好的设计是尽量避免出现同步问题,因为一旦出现同步,必然要加锁,加锁之后的执行顺序其实是穿行的,又因为有开锁解锁的过程,因此其效率比串行执行效率更差,效率跟逻辑是一个矛盾体,只能找到平衡点,不可能找到绝对完美的解决方案,自旋锁(比如OSSpinLockLock)已被证明不安全,同步锁简单,性能差,nslock 性能略好,dispatch_semphone 性能最好。

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

----cup轮询片

假如现在只有一个cpu 核心,但是我现在想线程并发,怎么办? 假如有1、2 两个线程想并发

cpu 就会执行1下1  然后再执行1下2  然后再执行1下1.。。。。。。。。。。 如此不停的切换,这就是cpu 轮询 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,032评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,041评论 1 32
  • 最全的iOS面试题及答案 iOS面试小贴士 ———————————————回答好下面的足够了-----------...
    大罗Rnthking阅读 947评论 0 2
  • 晚上带着公婆回妈妈家门口的浴池洗澡,洗完澡回家玩,走到一半忽然好想在妈妈家住一晚,于是又抱着孩子上楼了,回来的一瞬...
    Lily5566阅读 147评论 1 0
  • 负面思维人人都有,所以负面的人也不可怕,可怕的是负面的人不认为自己是负面的。发现问题既能解决问题。但连问题都不知道...
    熠煊妈妈阅读 171评论 0 0