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

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

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


当前回答

我在这里找到了原子和非原子财产的一个很好的解释。以下是一些相关文本:

“原子”意味着它不能分解。在OS/编程术语中,原子函数调用是一个不能中断的调用——必须执行整个函数,并且在完成之前,不能通过操作系统通常的上下文切换将其从CPU中移出。万一你不知道:由于CPU一次只能做一件事,操作系统会在很小的时间段内对所有正在运行的进程轮流访问CPU,从而产生多任务的错觉。CPU调度器可以(并且确实)在进程执行的任何时间点中断进程,即使是在函数调用中间。因此,对于像更新共享计数器变量这样的操作,如果两个进程可以同时尝试更新变量,则必须“原子”地执行这些操作,即,每个更新操作必须完整完成,然后才能将任何其他进程交换到CPU上。所以我猜测,在这种情况下,原子意味着属性读取器方法不能被中断,实际上意味着该方法读取的变量不能中途更改其值,因为其他线程/调用/函数被交换到CPU上。

因为原子变量不能被中断,所以它们在任何时候包含的值(线程锁)都保证不会被破坏,尽管确保这个线程锁会使对它们的访问变慢。另一方面,非原子变量不能保证这一点,但确实提供了快速访问的奢侈。总之,当您知道变量不会被多个线程同时访问时,可以使用非原子的,这样可以加快速度。

其他回答

原子:通过使用NSLOCK锁定线程来确保线程安全。

非原子:由于没有线程锁定机制,因此无法确保线程安全。

原子财产(Atomic properties):-当一个变量被赋值为原子属性时,意味着它只有一个线程访问权限,并且它是线程安全的,从性能角度来看速度较慢,那么它将具有默认行为。

非原子财产:-当一个变量被赋值为非原子属性时,这意味着它具有多线程访问权限,并且它不是线程安全的,从性能角度来看速度很快,它将具有默认行为,并且当两个不同的线程要同时访问该变量时,它将产生意外的结果。

最后两个是相同的;“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。

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

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

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

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

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