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

属性声明中atomicnonatomic是什么意思?

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

这三个之间的操作区别是什么?

答案

最后两个是相同的; “atomic” 是默认行为( 请注意,它实际上不是关键字;仅通过不存在nonatomic atomic 来指定 - 在最近的 llvm / clang 版本中将atomic作为关键字添加)。

假定您正在 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@一起的一起的 @@@@@@@@@@ @合成的基础上实现的,原子的或非原子的来改变所生成的代码。如果您正在编写自己的 setter / getter,则原子 / 非原子 / 保留 / 分配 / 复制仅是建议性的。 (注意:@synthesize 现在是 LLVM 的最新版本中的默认行为。也无需声明实例变量;它们也将自动合成,并且名称前会带有_ ,以防止意外的直接访问。)

使用 “atomic”,合成的 setter / getter 将确保始终从 getter 返回或由 setter 设置整个值,而不管任何其他线程上的 setter 活动如何。也就是说,如果线程 A 在 getter 的中间,而线程 B 在调用 setter 时,实际的可行值(很可能是自动释放的对象)将返回给 A 中的调用者。

nonatomic ,不会做出任何此类保证。因此, nonatomic比 “原子” 要快得多。

什么 “原子” 就是让有关线程安全的任何保证。如果线程 A 在调用线程 B 的同时同时调用线程 B 和 C 调用具有不同值的 setter,则线程 A 可能会获得返回的三个值中的任何一个 - 在调用任何 setter 之前的值之一,或者将其中一个值传递给 setter 同样,对象可能以 B 或 C 中的值结尾,无法分辨。

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

添加到此:

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

考虑:

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

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

为了解决这个问题,您需要一个事务模型 。即某种其他类型的同步和 / 或排除,允许在更新从属属性时排除对fullName访问。

Apple 的文档中对此进行了说明,但以下是一些实际情况的示例。

请注意,没有 “atomic” 关键字,如果未指定 “nonatomic”,则该属性为 atomic,但是显式指定 “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。

实际上,根据属性是标量值还是对象以及保留,复制,只读,非原子等相互作用的方式,这些事物的工作方式存在大量不同的变体。通常,属性合成器只知道如何对所有组合执行 “正确的事情”。

原子

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

非原子

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

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

假设有一个名为 “名” 原子串属性,如果你调用[self setName:@"A"]从线程 A,调用[self setName:@"B"]从线程 B,并调用[self name]从线程 C,然后对不同线程的所有操作将依次执行,这意味着如果一个线程正在执行 setter 或 getter,则其他线程将等待。

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

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

该问题的其他出色答案已经很好地定义了语法和语义。由于执行性能不够详细,我将添加我的答案。

这 3 个功能之间有什么区别?

我一直都将 atomic 作为默认值感到非常好奇。在我们的抽象层次上,使用原子属性作为类来实现 100%线程安全是一种极端的情况。对于真正正确的多线程程序,几乎可以肯定需要程序员的干预。同时,性能特征和执行尚未深入。已经写了一些严重的多线程程序,多年来,我一直宣称我的属性nonatomic的全部时间,因为原子是不懂事用于任何目的。在讨论这个问题的原子和非原子特性的详细信息期间,我进行了一些分析,并遇到了一些奇怪的结果。

执行

好。我要清除的第一件事是锁定实现是实现定义和抽象的。 Louis 在他的示例中使用@synchronized(self) -我已将其视为常见的混淆源。该实现实际上并不使用@synchronized(self) ; 它使用对象级自旋锁 。 Louis 的插图适合使用我们都熟悉的结构进行高级插图,但是重要的是要知道它不使用@synchronized(self)

另一个区别是原子属性将保留 / 释放对象在吸气剂中的循环。

性能

这是有趣的部分:在某些情况下,使用无争议 (例如单线程)情况下的原子属性访问可以实现非常快的性能。在不太理想的情况下,使用原子访问的开销可能nonatomic原子访问开销的 20 倍以上。对于三字节结构(2.2 GHz Core i7四核,x86_64),使用 7 个线程的竞争情况要慢 44 倍。三字节结构是一个非常慢的属性的示例。

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

在有争议的情况下,物体也可能超过 50 次。

由于实现过程中存在许多优化和变体,因此很难衡量在这些情况下的实际影响。您可能经常会听到类似 “请信任它,除非您进行分析并发现问题” 之类的内容。由于具有抽象级别,因此实际上很难衡量实际影响。从配置文件中收集实际成本可能非常耗时,并且由于抽象,因此非常不准确。同样,ARC vs MRC 也可以发挥很大的作用。

因此,让我们退后一步, 而不是专注于属性访问的实现,我们将包括objc_msgSend之类的常见可疑objc_msgSend ,并在一些无争议的情况下(以秒为单位的值)检查对NSString getter 的多次调用的一些实际高级结果:

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

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

尽管我非常关注性能,但我还是说语义第一! 。同时,对于许多项目而言,性能并不是优先事项。但是,知道执行细节和所用技术的成本肯定不会受到损害。您应该根据需要,目的和能力使用正确的技术。希望这将节省您几个小时的比较,并帮助您在设计程序时做出更明智的决定。

原子 = 线程安全

非原子 = 无线程安全

线程安全性:

如果实例变量在从多个线程访问时正确运行,则它们是线程安全的,而不管运行时环境对那些线程的执行进行调度或交织,并且调用代码部分没有其他同步或其他协调。

在我们的上下文中:

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

在哪里使用atomic

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

atomic含义:

不如nonatomic nonatomic速度快,因为nonatomic不需要在运行时nonatomic进行任何看门狗工作。

在哪里使用nonatomic

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

我发现原子和非原子性的一个很好的解释放在这里 。这是来自同一方面的一些相关文本:

“原子” 意味着它不能被分解。用 OS / 编程术语来说,原子函数调用是不能中断的 - 整个函数必须执行,并且直到完成后,才通过 OS 的常规上下文切换将其交换出 CPU。万一您不知道:由于 CPU 一次只能做一件事,因此 OS 会在很短的时间范围内将对 CPU 的访问权循环到所有正在运行的进程,从而给人一种多任务的错觉 。 CPU 调度程序可以(并且确实)在执行的任何时刻中断进程,即使在中间函数调用中也是如此。因此,对于诸如更新共享计数器变量之类的操作(其中两个进程可以尝试同时更新变量),必须 “以原子方式” 执行它们,即,每个更新操作必须完整完成,然后才能将任何其他进程交换到该进程上。中央处理器。

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

因为atomic变量不能被中断,所以确保它们在任何时候所包含的值(线程锁定)都不会被破坏 ,尽管确保这种线程锁定会使访问它们的速度变慢。另一方面, non-atomic变量不提供这样的保证,但确实提供了更快的访问权限。总结起来,当您知道多个线程不会同时访问您的变量并加快处理速度时,请使用non-atomic操作。

在阅读了很多文章,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是默认行为
    • 在非垃圾回收环境中(例如,在使用保留 / 释放 / 自动释放时),原子访问器将使用锁来确保另一个线程不会干扰该值的正确设置 / 获取。
    • 它实际上不是关键字。

    例:

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

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

    例:

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

原子的

原子保证对属性的访问将以原子方式执行。例如,它总是返回完全初始化的对象,一个线程上属性的任何获取 / 设置都必须先完成,然后另一个线程才能访问它。

如果您想象一下以下功能同时在两个线程上发生,那么您会明白为什么结果会很不好。

-(void) setName:(NSString*)string
{
  if (name)
  {
    [name release]; 
    // what happens if the second thread jumps in now !?
    // name may be deleted, but our 'name' variable is still set!
    name = nil;
  }

  ...
}

优点:在多线程的情况下,每次返回完全初始化的对象是最佳选择。

缺点:性能下降,执行速度稍慢

非原子:

与 Atomic 不同,它不能确保每次都完全初始化对象。

优点:执行速度极快。

缺点:在多线程的情况下,可能会产生垃圾值。

首先最简单的答案:您的后两个示例之间没有区别。默认情况下,属性访问器是原子的。

在非垃圾回收环境中(例如,在使用保留 / 释放 / 自动释放时),原子访问器将使用锁来确保另一个线程不会干扰该值的正确设置 / 获取。

有关创建更多信息以及创建多线程应用程序时的其他注意事项,请参阅 Apple 的 Objective-C 2.0 文档的 “ 性能和线程 ” 部分。