题 原子和非原子属性之间有什么区别?


做什么 atomic 和 nonatomic 在财产声明中意味着什么?

@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
@property(retain) UITextField *userName;

这三者之间的运作区别是什么?


1723
2018-02-26 02:31


起源


developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/... - KKendall
@Alex Wayne - 我有一个问题,我发布了但没有人回答这个问题。你能帮我这个吗?问题链接 - stackoverflow.com/questions/35769368/... - DJ1


答案:


最后两个是相同的; “atomic”是默认行为(请注意,它实际上不是关键字;它仅由缺少指定 nonatomic  - atomic 在最新版本的llvm / clang中添加了关键字。

假设你是@synthesizing方法实现,原子与非原子会改变生成的代码。如果您正在编写自己的setter / getter,则atomic / nonatomic / retain / assign / copy仅仅是建议性的。 (注意:@synthesize现在是最近版本的LLVM中的默认行为。也没有必要声明实例变量;它们也将自动合成,并且将具有 _ 以他们的名字为前缀以防止意外直接访问)。

使用“原子”,合成的setter / getter将确保a 整个 无论任何其他线程上的setter活动如何,值始终从getter返回或由setter设置。也就是说,如果线程A位于getter的中间,而线程B调用setter,则实际可行的值 - 一个自动释放的对象,很可能 - 将返回给A中的调用者。

nonatomic,没有这样的保证。从而, nonatomic 比“原子”快得多。

什么是“原子”  对线程安全做出任何保证。如果线程A与线程B同时调用getter并且C调用具有不同值的setter,则线程A可以获得返回的三个值中的任何一个 - 在调用任何setter之前的值或者传递给setter的任一值在B和C中。同样,对象可能最终得到B或C的值,无法分辨。

确保数据完整性 - 多线程编程的主要挑战之一 - 是通过其他方式实现的。

添加到此:

atomicity 当多个依赖属性在起作用时,单个属性也不能保证线程安全。

考虑:

 @property(atomic, copy) NSString *firstName;
 @property(atomic, copy) NSString *lastName;
 @property(readonly, atomic, copy) NSString *fullName;

在这种情况下,线程A可以通过调用重命名对象 setFirstName: 然后打电话 setLastName:。同时,线程B可以调用 fullName 在线程A的两个调用之间,将接收新的名字和旧的姓氏。

要解决这个问题,你需要一个 交易模型。即一些其他类型的同步和/或排除,允许一个人排除访问权限 fullName 而依赖属性正在更新。


1668
2018-02-26 06:40



鉴于任何线程安全的代码将执行自己的锁定等,您何时想使用原子属性访问器?我在思考一个很好的例子时遇到了麻烦。 - Daniel Dickison
@bbum有道理。我喜欢你对另一个答案的评论,即线程安全更像是一个模型级别的问题。从IBM线程安全定义: ibm.co/yTEbjY “如果一个类被正确实现,这是另一种说法符合其规范的方式,那个类的对象上的任何操作序列(公共字段的读取或写入以及对公共方法的调用)都应该能够放置该对象进入无效状态,观察对象处于无效状态,或违反任何类的不变量,先决条件或后置条件。“ - Ben Flynn
这是一个类似于@StevenKramer的例子:我有一个 @property NSArray* astronomicalEvents; 列出我想在UI中显示的数据。当应用程序启动时,指针指向空数组,然后应用程序从Web中提取数据。当Web请求完成时(在不同的线程中),应用程序构建一个新数组,然后以原子方式将该属性设置为新的指针值。它是线程安全的,我不必编写任何锁定代码,除非我遗漏了一些东西。对我来说似乎非常有用。 - bugloaf
@HotLicks另一个有趣的;在某些体系结构上(不记得哪一个),作为参数传递的64位值可能会在寄存器中传递一半而在堆栈中传递一半。 atomic 防止跨线程半值读取。 (这是一个有趣的错误追踪。) - bbum
@congliu线程A返回一个没有的对象 retain/autorelease 舞蹈。线程B释放对象。线程A去了 繁荣。 atomic 确保线程A具有返回值的强引用(+1保留计数)。 - bbum


这在Apple的解释中有所解释 文件,但下面是一些实际发生的例子。请注意,没有“atomic”关键字,如果未指定“nonatomic”,则属性为atomic,但明确指定“atomic”将导致错误。

//@property(nonatomic, retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

现在,原子变体有点复杂:

//@property(retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName_ retain];
      [userName release];
      userName = userName_;
    }
}

基本上,原子版本必须采取锁定以保证线程安全,并且还会碰撞对象的引用计数(以及自动释放计数以平衡它),以便保证对象存在对象,否则存在如果另一个线程正在设置该值,则是一个潜在的竞争条件,导致引用计数降至0。

根据属性是标量值还是对象,以及如何保留,复制,只读,非原子等交互,实际上有很多不同的变体如何工作。一般来说,属性合成器只知道如何为所有组合做“正确的事”。


342
2018-02-26 06:24



并不是说锁没有“保证线程安全”。 - Jonathan Sterling
@Louis Gerbarg:我相信如果您尝试分配同一个对象(即:userName == userName_),您的(非原子,保留)setter版本将无法正常工作 - Florin
你的代码有点误导;有 没有 保证同步的原子getter / setter。重要的是,@property (assign) id delegate; 在任何事情上都没有同步(iOS SDK GCC 4.2 ARM -Os),这意味着之间有一场比赛 [self.delegate delegateMethod:self]; 和 foo.delegate = nil; self.foo = nil; [super dealloc];。看到 stackoverflow.com/questions/917884/... - tc.
@fyolnish我不知道是什么 _val/val 是,但不是,不是真的。原子的吸气剂 copy/retain 属性需要确保它不返回由于在另一个线程中调用setter而其refcount变为零的对象,这实际上意味着它需要读取ivar,保留它同时确保setter没有被覆盖并释放它,然后自动释放它以平衡保留。这基本上意味着 都 getter和setter必须使用一个锁(如果内存布局是固定的,它应该可以使用CAS2指令;唉 -retain 方法调用)。 - tc.
@tc这已经有一段时间但是我打算写的可能是这样的: gist.github.com/fjolnir/5d96b3272c6255f6baae 但是,有可能在setFoo:return之前读取旧值,并在读者返回之前释放。但也许如果setter使用-autorelease而不是-release,那就可以解决这个问题。 - Fjölnir


原子

  • 是默认行为
  • 在另一个进程访问变量之前,将确保CPU完成当前进程
  • 并不快,因为它确保完整的过程完成

非原子

  • 不是默认行为
  • 更快(对于合成代码,即对于使用@property和@synthesize创建的变量)
  • 不是线程安全的
  • 当两个不同的进程同时访问同一个变量时,可能会导致意外行为

148
2018-05-25 10:56





理解差异的最佳方法是使用以下示例。

假设有一个名为“name”的原子字符串属性,如果你打电话 [self setName:@"A"] 从线程A,调用 [self setName:@"B"] 从线程B,并打电话 [self name] 从线程C开始,不同线程上的所有操作都将按顺序执行,这意味着如果一个线程正在执行setter或getter,那么其他线程将等待。

这使得属性“name”读/写安全,但如果另一个线程D调用 [name release] 同时这个操作可能会导致崩溃,因为这里没有涉及setter / getter调用。这意味着对象是读/写安全的(ATOMIC),但不是线程安全的,因为另一个线程可以同时向对象发送任何类型的消息。开发人员应确保此类对象的线程安全性。

如果属性“name”是非原子的,那么上面例子中的所有线程--A,B,C和D将同时执行,产生任何不可预测的结果。在原子的情况下,A,B或C中的任何一个将首先执行,但D仍然可以并行执行。


125
2018-01-31 18:36





这个问题的其他优秀答案已经很好地定义了语法和语义。因为 执行 和 性能 不详细,我会补充我的答案。

这3个有什么功能差异?

我一直认为原子作为一个非常好奇的默认。在我们工作的抽象级别,使用类的原子属性作为实现100%线程安全的工具是一个极端情况。对于真正正确的多线程程序,程序员的干预几乎肯定是必需的。同时,性能特征和执行还没有详细说明。多年来编写了一些大量多线程的程序后,我一直在声明我的属性 nonatomic整个时间因为原子对任何目的都不合理。在讨论原子和非原子属性的细节时 这个问题,我做了一些分析,遇到了一些好奇的结果。

执行

好。我想要澄清的第一件事是锁定实现是实现定义和抽象的。路易斯使用 @synchronized(self) 在他的例子中 - 我已经看到这是混乱的常见根源。实施没有 其实 使用 @synchronized(self);它使用对象级别 旋锁。路易斯的插图很适合使用我们都熟悉的结构的高级插图,但重要的是要知道它不使用 @synchronized(self)

另一个区别是原子属性将保留/释放你的对象在getter中。

性能

这是有趣的部分:使用原子属性访问的性能 无可争议 (例如,单线程)案例在某些情况下可能非常快。在不太理想的情况下,使用原子访问可能花费超过20倍的开销 nonatomic。虽然 争议 使用7个线程的情况对于三字节结构(2.2 GHz)来说慢了44倍 酷睿i7 四核,x86_64)。三字节结构是一个非常慢的属性的例子。

有趣的注意事项:三字节结构的用户定义访问器比合成的原子访问器快52倍;或84%的合成非原子访问器的速度。

有争议案件中的物品也可超过50次。

由于实施中的优化和变化的数量,在这些环境中测量实际影响是非常困难的。您可能经常听到类似“信任它,除非您分析并发现它是一个问题”的内容。由于抽象级别,实际上很难衡量实际影响。从配置文件收集实际成本可能非常耗时,并且由于抽象,非常不准确。同样,ARC vs MRC可以产生很大的不同。

让我们退后一步,  关注财产访问的实施,我们将包括通常的嫌疑人 objc_msgSend,并检查一些真实世界的高级别结果,许多调用a NSString 得到的 无可争议 案例(以秒为单位的值):

  • MRC |非原子的手动实现的getters:2
  • MRC |非原子的合成的吸气剂:7
  • MRC |原子|合成的吸气剂:47
  • ARC |非原子的合成的吸气剂:38(注意:ARC在这里添加ref count循环)
  • ARC |原子|合成的吸气剂:47

正如您可能已经猜到的那样,引用计数活动/循环是原子学和ARC下的重要贡献者。您还会看到有争议的案例存在更大的差异。

虽然我非常关注表现,但我仍然说 语义第一!。同时,对许多项目来说,性能是一个低优先级。但是,了解执行细节和您使用的技术成本肯定不会受到影响。您应该根据自己的需要,目的和能力使用正确的技术。希望这将为您节省几个小时的比较,并帮助您在设计程序时做出更明智的决策。


108
2017-08-18 09:47



MRC |原子|合成的吸气剂:47 ARC |原子|合成的吸气剂:47什么使它们相同? ARC不应该有更多的开销吗? - SDEZero
因此,如果原子属性不好,则它们是默认的。增加样板代码? - Kunal Balani
@ LearnCocos2D我刚刚在同一台机器上测试10.8.5,目标是10.8,对于单螺纹无争议情况 NSString 这不是不朽的: -ARC atomic (BASELINE): 100% -ARC nonatomic, synthesised: 94% -ARC nonatomic, user defined: 86% -MRC nonatomic, user defined: 5% -MRC nonatomic, synthesised: 19% -MRC atomic: 102%  - 今天的结果有点不同。我没有做任何事 @synchronized 比较。 @synchronized 在语义上是不同的,如果你有重要的并发程序,我认为它不是一个好工具。如果你需要速度,请避免 @synchronized。 - justin
你在网上有这个测试吗?我一直在这里加我的: github.com/LearnCocos2D/LearnCocos2D/tree/master/... - LearnCocos2D
@ LearnCocos2D我还没准备好供人食用,对不起。 - justin


原子=线程安全

非原子 =没有线程安全

螺纹安全:

如果实例变量在从多个线程访问时行为正确,则它们是线程安全的,无论运行时环境是否调度或交错执行这些线程,并且调用代码没有额外的同步或其他协调。

在我们的背景下:

如果线程更改了实例的值,则更改的值可用于所有线程,并且只有一个线程可以一次更改该值。

在哪里使用 atomic

如果要在多线程环境中访问实例变量。

的含义 atomic

没有那么快 nonatomic 因为 nonatomic 从运行时开始不需要任何监视器工作。

在哪里使用 nonatomic

如果实例变量不会被多个线程更改,则可以使用它。它提高了性能。


89
2017-07-10 13:07



你在这里说的一切都是正确的,但对于今天的节目,最后一句话基本上是“错误的”Dura。你真的很难想象用这种方式“提高性能”。 (我的意思是,在你进入光年之前,你会“不使用ARC”,“不使用NSString,因为它很慢!”等等。)举一个极端的例子,就像说“团队,不要在代码中添加任何评论,因为它会减慢我们的速度。“没有现实的开发流程,您可能希望(不存在的)理论性能增益不可靠。 - Fattie
@JoeBlow这是一个你可以在这里验证它的事实 developer.apple.com/library/mac/documentation/Cocoa/Conceptual/... - Durai Amuthan.H
很好地解释(y) - Sunil Targe


我发现原子和非原子属性的解释非常好 这里。以下是相同的相关文字:

'原子'意味着它不能被分解。   在OS /编程术语中,原子函数调用是一个不能被中断的函数 - 整个函数必须执行,并且不会被操作系统通常的上下文切换换出CPU直到它完成。以防您不知道:由于CPU一次只能执行一项操作,因此操作系统会在很短的时间内将CPU的访问权限转移到所有正在运行的进程,以便 错觉 多任务处理。 CPU调度程序可以(并且确实)在执行过程中的任何时刻中断进程 - 即使在函数调用期间也是如此。因此,对于更新共享计数器变量的操作,其中两个进程可以尝试同时更新变量,它们必须“原子地”执行,即每个更新操作必须完全完成才能将任何其他进程交换到中央处理器。

所以我猜测在这种情况下原子意味着属性读取器方法不能被中断 - 实际上意味着方法读取的变量不能在中途改变它们的值,因为其他一些线程/调用/函数得到了交换到CPU上。

因为 atomic 变量不能被中断,它们在任何一点所包含的值都是(线程锁定)保证的 清廉但是,确保此线程锁定可以更慢地访问它们。 non-atomic 另一方面,变量没有这样的保证,但确实提供了更快捷的访问。总结一下,去吧 non-atomic 当你知道多个线程不会同时访问你的变量并加快速度时。


67
2018-02-24 05:17





在阅读了这么多文章后,Stack Overflow发布并制作了演示应用程序以检查变量属性属性,我决定将所有属性信息放在一起:

  1. atomic             //默认
  2. nonatomic
  3. strong = retain        //默认
  4. weak = unsafe_unretained
  5. retain
  6. assign             //默认
  7. unsafe_unretained
  8. copy
  9. readonly
  10. readwrite                 //默认

在文章中 iOS中的变量属性属性或修饰符 你可以找到所有上述属性,这肯定会对你有所帮助。

  1. atomic

    • atomic 表示只有一个线程访问变量(静态类型)。
    • atomic 是线程安全的。
    • 但它的表现很慢
    • atomic 是默认行为
    • 非垃圾收集环境中的原子访问器(即使用retain / release / autorelease时)将使用锁来确保另一个线程不会干扰正确设置/获取值。
    • 它实际上不是关键字。
       

    例:

        @property (retain) NSString *name;
    
        @synthesize name;
    
  2. nonatomic

    • nonatomic 表示多线程访问变量(动态类型)。
    • nonatomic 线程不安全。
    • 但它的性能很快
    • nonatomic 不是默认行为。我们需要添加 nonatomic 属性属性中的关键字。
    • 当两个不同的进程(线程)同时访问同一个变量时,它可能会导致意外行为。
       

    例:

        @property (nonatomic, retain) NSString *name;
    
        @synthesize name;
    

61
2018-03-21 07:10



分配和强/保留两者是如何默认的? - BangOperator
强有力的ARC,在ARC之前保留默认值 - abdullahselek