在属性声明中,atomic
和nonatomic
是什么意思?
@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
@property(retain) UITextField *userName;
这三者之间的操作区别是什么?
在属性声明中,atomic
和nonatomic
是什么意思?
@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
@property(retain) UITextField *userName;
这三者之间的操作区别是什么?
@property(atomic, copy) NSString *firstName;
@property(atomic, copy) NSString *lastName;
@property(readonly, atomic, copy) NSString *fullName;
在这种情况下,线程A可能通过调用setFirstName:
然后调用setLastName:
来重命名对象。与此同时,线程B可能会在线程A的两次调用之间调用fullName
,并将得到新的名字和旧姓氏组合起来的结果。
为了解决这个问题,您需要一个事务性模型。也就是说,需要一些其他类型的同步和/或排除,以允许在更新相关属性时排除对fullName
的访问。@property NSArray* astronomicalEvents;
,它列出了我想在UI中显示的数据。当应用程序启动时,指针指向一个空数组,然后应用程序从Web上获取数据。当Web请求完成(在不同的线程中)时,应用程序会构建一个新数组,然后原子地将属性设置为新的指针值。这是线程安全的,我不必编写任何锁定代码,除非我漏掉了什么。对我来说看起来相当有用。 - bugloafretain/autorelease
技巧。线程B释放该对象。线程A出现问题。atomic
确保线程A对返回值有一个强引用(即+1的保留计数)。 - bbum这在苹果的文档中有详细解释,但以下是一些实际发生的情况示例。
请注意,没有 "atomic" 关键字,如果你不指定 "nonatomic",那么属性是原子的,但显式指定 "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。
实际上,这些东西有很多不同的变体,具体取决于属性是标量值还是对象,以及如何处理保留、复制、只读、非原子等相互作用。总的来说,属性合成器知道如何对所有组合做出“正确的事情”。
@property (assign) id delegate;
没有在任何东西上同步(iOS SDK GCC 4.2 ARM -Os
),这意味着 [self.delegate delegateMethod:self];
和 foo.delegate = nil; self.foo = nil; [super dealloc];
之间存在竞争。详见 https://dev59.com/ZnNA5IYBdhLWcg3wjOlS。 - tc._val
/val
是什么,但是不是。原子 copy
/retain
属性的 getter 需要确保它不会返回一个对象,因为在另一个线程中调用了 setter 导致其引用计数变为零,这基本上意味着它需要读取 ivar,保留它同时确保 setter 没有覆盖并释放它,然后自动释放它以平衡保留。这基本上意味着 getter 和 setter 都必须使用锁定(如果内存布局固定,则应该可以使用 CAS2 指令;遗憾的是 -retain
是一个方法调用)。 - tc.[self setName:@"A"]
,从线程B调用[self setName:@"B"]
,并从线程C调用[self name]
,则不同线程上的所有操作都将按顺序执行,这意味着如果一个线程正在执行设置器或获取器,则其他线程将等待。
这使属性“name”具有读/写安全性,但如果另一个线程D同时调用[name release]
,则此操作可能会导致崩溃,因为此处没有涉及设置器/获取器调用。这意味着对象具有读/写安全性(原子性),但不具备线程安全性,因为其他线程可以同时向对象发送任何类型的消息。开发人员应确保对此类对象进行线程安全性。
如果属性“name”为非原子性,则上述示例中的所有线程 - A、B、C和D都将同时执行,从而产生任何不可预测的结果。在原子性情况下,A、B或C中的一个将首先执行,但仍然可以并行执行D。语法和语义已经由其他优秀的答案详细解释了。因为“执行”和“性能”没有被充分详细说明,所以我想要补充我的答案。
这3种属性之间的功能差异是什么?
我一直认为原子属性作为默认选项相当奇怪。在我们工作的抽象级别上,使用原子属性作为实现100%线程安全的手段是一个特例。对于真正正确的多线程程序,几乎肯定需要程序员干预。同时,性能特征和执行尚未被深入详述。多年来,我编写了一些高度多线程的程序,我一直将我的属性声明为nonatomic
,因为原子属性对任何目的都不明智。在讨论原子和非原子属性的细节时,我遇到了一些有趣的结果(链接)。
执行
首先,我想澄清的第一件事是锁定实现是由实现定义的且被抽象化的。Louis 在他的示例中使用了@synchronized(self)
,我曾看到过这是一种常见的困惑源泉。实现实际上不使用@synchronized(self)
;它使用对象级的自旋锁。Louis 的示例对于使用我们都熟悉的构造进行高层次说明非常好,但是了解它并不使用@synchronized(self)
很重要。
另一个区别是原子属性将在getter方法中保留/释放您的对象。
性能
有趣的是,在非争用(例如单线程)情况下使用原子属性访问的性能在某些情况下确实非常快。在不太理想的情况下,使用原子访问的开销可能比nonatomic
的开销要高出20倍以上。其中使用7个线程的争用情况,对于三字节结构来说慢了44倍(2.2 GHz的Core i7 Quad Core,x86_64)。三字节结构是一个非常慢的属性的例子。
有趣的是三字节结构的用户定义访问器比合成的原子访问器快52倍;或者相当于合成的非原子访问器的84%的速度。
在竞争的情况下,对象的开销也可能超过50倍。
由于优化和实现的变化很多,所以在这些情况下测量真实世界的影响是相当困难的。你经常会听到类似于“除非你通过分析发现它是一个问题,否则就信任它”。由于抽象级别,实际上很难测量实际的影响。从分析数据中获取实际成本可能非常耗时,并且由于抽象层次的原因,结果也可能不太准确。此外,ARC与MRC也会产生很大的差异。
因此,让我们回到高层次的结果,不关注属性访问的实现细节,我们将包括像objc_msgSend
这样的常见操作,并检查在非争用情况下多次调用NSString
获取器的一些真实世界的高层次结果(单位为秒):
你可能已经猜到了,在原子操作和自动引用计数(ARC)中,引用计数的活动/循环是一个重要的贡献因素。在有争议的情况下,你也会看到更大的差异。
尽管我非常关注性能,但我仍然说先确保语义! 同时,对于许多项目而言,性能并不是优先考虑的问题。 然而,了解所使用的技术的执行细节和成本肯定是有帮助的。 你应该根据你的需求、目的和能力选择正确的技术。希望这能为你节省几个比较的小时,并帮助你在设计程序时做出更好的决策。
NSString
:
-ARC原子操作(基准):100% -ARC非原子操作,合成:94% -ARC非原子操作,用户定义:86% -MRC非原子操作,用户定义:5% -MRC非原子操作,合成:19% -MRC原子操作:102%
-- 今天的结果有些不同。我没有进行任何synchronized
比较。@synchronized
在语义上是不同的,如果您有非平凡的并发程序,我不认为它是一个好工具。如果需要速度,请避免使用synchronized
。 - justin原子性 = 线程安全
非原子性 = 不具备线程安全性
如果实例变量在多个线程中正确访问时,不受运行时环境的调度或交错执行的影响,并且在调用代码方面没有其他同步或协调的情况下,那么它是线程安全的。
如果一个线程更改了实例的值,则所有线程都可以访问更改后的值,同时只有一个线程可以更改该值。
atomic
:如果实例变量将在多线程环境中被访问,则应使用 atomic
。
Atomic
的含义: atomic
不如 nonatomic
快,因为 nonatomic
不需要运行时进行任何监视工作。
nonatomic
:如果实例变量不会被多个线程更改,则可以使用nonatomic
。这会提高性能。
在阅读了许多文章、Stack Overflow帖子并制作演示应用程序以检查变量属性特性后,我决定将所有属性信息汇总:
atomic
// 默认值nonatomic
strong = retain
// 默认值weak = unsafe_unretained
retain
assign
// 默认值unsafe_unretained
copy
readonly
readwrite
// 默认值在文章iOS中的变量属性特性或修饰符中,您可以找到上述所有属性,并且这肯定会有所帮助。
atomic
atomic
表示只有一个线程访问该变量(静态类型)。atomic
是线程安全的。atomic
是默认行为。示例:
@property (retain) NSString *name;
@synthesize name;
nonatomic
nonatomic
表示变量可以被多个线程访问(动态类型)。nonatomic
不保证线程安全。nonatomic
不是默认行为。我们需要在属性中添加nonatomic
关键字。示例:
@property (nonatomic, retain) NSString *name;
@synthesize name;
'atomic' 意味着其不能被拆分。 在操作系统/编程术语中,原子函数调用是指无法被中断的函数 - 整个函数必须被执行,并且在完成之前不能被操作系统的常规上下文切换交换出CPU。只是以防万一您不知道:由于CPU一次只能做一件事情,所以操作系统会在小的时间片内轮流让所有运行中的进程访问CPU,以给予多任务的幻觉。CPU调度程序可以(并且确实)在执行过程的任何时候中断进程 - 甚至在函数调用中途。因此,对于像更新共享计数器变量这样的操作,如果两个进程尝试同时更新变量,则它们必须以“原子方式”执行,即每个更新操作必须在任何其他进程可以被交换到CPU之前全部完成。
因此,我猜在这种情况下,“原子”意味着属性读取方法无法被中断 - 实际上意味着被方法读取的变量不能在中途改变其值,因为某些其他线程/调用/函数被交换到CPU上。
atomic
变量由于不能被中断,所以在任何时候都保证它们所包含的值是未被破坏的(线程锁),但是确保此线程锁会使访问它们变慢。non-atomic
变量则没有这样的保证,但提供了更快的访问速度。总之,当您知道您的变量不会被多个线程同时访问并且需要加快速度时,请使用non-atomic
。
原子性保证对属性的访问将以原子方式进行。例如,它总是返回一个完全初始化的对象,在一个线程上对属性的任何get/set必须在另一个线程可以访问它之前完成。
如果你想象以下函数同时在两个线程上发生,你会明白为什么结果不会很好。
-(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;
}
...
}
优点: 每次返回完全初始化的对象,使其成为多线程情况下的最佳选择。
缺点: 性能受损,使执行速度稍微慢一些。
与原子性不同,它不能确保每次返回完全初始化的对象。
优点: 执行速度极快。
缺点: 在多线程情况下存在垃圾值的可能性。