在属性声明中,原子和非原子意味着什么?

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

这三者之间的操作差异是什么?


当前回答

在开始之前:您必须知道内存中的每个对象都需要从内存中解除分配,才能生成新的写入程序。你不能像在纸上那样简单地在上面写字。您必须首先擦除(解除锁定)它,然后才能写入它。如果在擦除完成(或完成一半)的时候,还没有写入任何内容(或写入一半),而您尝试读取它可能会非常困难!原子和非原子帮助你以不同的方式处理这个问题。

首先阅读这个问题,然后阅读Bbum的答案。此外,请阅读我的摘要。


原子将始终保证

如果两个不同的人想同时阅读和写作,你的论文就不会燃烧!-->即使在竞争条件下,您的应用程序也不会崩溃。如果一个人正在尝试写,并且只写了8个字母中的4个,那么没有人可以在中间读,只有当所有8个字母都写了之后才能进行读取-->“仍在写的线程”上不会发生读取(get),即如果要写入8个字节到字节,并且只写入了4个字节,到那时为止,您不允许从中读取。但既然我说它不会崩溃,那么它将从自动释放对象的值中读取。如果在写作之前,你已经删除了之前写在纸上的内容,然后有人想要阅读,你仍然可以阅读。怎样您将从类似于Mac OS垃圾箱的内容中读取内容(因为垃圾箱尚未被100%擦除……它处于边缘状态)--->如果ThreadA在ThreadB已解除分配写入时读取,您将从ThreadB的最终完全写入值中获得一个值,或者从自动释放池中获得一些值。

保留计数是Objective-C中管理内存的方式。创建对象时,其保留计数为1。当您发送对象为保留消息,其保留计数递增1。什么时候如果向对象发送释放消息,则其保留计数将递减通过1。向对象发送自动释放消息时,其保留计数在将来的某个阶段减1。如果对象保留计数减为0,则解除分配。

Atomic不能保证线程安全,但它对实现线程安全很有用。线程安全与您如何编写代码/从哪个线程队列读取/写入代码有关。它只保证不可崩溃的多线程。


什么多线程和线程安全不同吗?

对多线程意味着:多个线程可以同时读取一段共享数据,我们不会崩溃,但这并不能保证您不会从非自动释放的值读取数据。通过线程安全,可以保证您读取的内容不会自动释放。默认情况下,我们没有使所有内容都原子化的原因是,存在性能成本,而且大多数情况下并不真正需要线程安全。我们的代码有几个部分需要它,对于这几个部分,我们需要使用锁、互斥锁或同步以线程安全的方式编写代码。


非原子的

因为没有像Mac OS垃圾箱这样的东西,所以没有人关心你是否总是得到一个值(<--这可能会导致崩溃),也没有人会在意是否有人试图在你的写作过程中读到一半(虽然在内存中写的一半与在纸上写的一半有很大不同,但在内存中,它可能会给你一个前所未有的愚蠢值,而在纸上,你只能看到一半的内容)-->不保证不会崩溃,因为它不使用自动释放机制。不保证读取完整的写入值!比原子更快


总体而言,它们在两个方面有所不同:

崩溃或不是因为有或没有自动释放池。允许在“尚未完成写入或空值”的中间读取,或不允许且仅允许在值完全写入时读取。

其他回答

原子属性确保保留完全初始化的值,而不管有多少线程在其上执行getter和setter。

非原子属性指定合成访问器只需直接设置或返回一个值,而不保证从不同线程同时访问同一个值会发生什么。

最后两个是相同的;“atomic”是默认行为(请注意,它实际上不是一个关键字;它仅在不存在非atomic的情况下被指定——在最近版本的llvm/claung中,atomic被添加为关键字)。

假设您正在@合成方法实现,原子与非原子将更改生成的代码。如果您正在编写自己的setter/getter,原子/非原子/保留/分配/复制只是建议。(注意:@synthey现在是LLVM最新版本中的默认行为。也不需要声明实例变量;它们也会自动合成,并且在名称前加一个_,以防止意外的直接访问)。

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

在非原子中,没有这样的保证。因此,非原子比“原子”快得多。

“原子”不做的是保证线程安全。如果线程A同时调用getter,而线程B和C使用不同的值调用setter,那么线程A可能会得到返回的三个值中的任何一个值——在调用setter之前的值,或者在B和C中传递给setter的值。同样,对象可能会以B或C的值结束,这是无法分辨的。

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

添加到此:

当使用多个依赖财产时,单个属性的原子性也不能保证线程安全。

考虑:

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

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

要解决这个问题,您需要一个事务模型。也就是说,某些其他类型的同步和/或排除允许在更新依赖财产时排除对fullName的访问。

在单行中:

原子是线程安全的。非原子是线程不安全的。

原子=螺纹安全

非原子=无线程安全

螺纹安全:

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

在我们的背景下:

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

在何处使用原子:

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

原子的含义:

没有非原子的速度快,因为非原子的运行时不需要任何看门狗的工作。

在何处使用非原子:

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

苹果的文档中对此进行了解释,但下面是一些实际发生情况的示例。

请注意,没有“atomic”关键字,如果不指定“nonatomic(非原子)”,则属性是原子的,但显式指定“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。

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