对于 ObjC 属性 -两者都不是线程安全的。
Atomic 更能抵抗线程错误。总的来说,这是一个奇怪的默认设置。您喜欢原子的场景很少。原子可以增加正确性的概率,但它的级别太低,不能被认为是适当锁定机制的替代品。因此,如果您需要线程安全,您仍然需要在原子读/写之上的其他一些同步原语。如果您不需要线程安全(例如,实例是不可变的或打算仅从主线程运行),则 atomic 不会添加任何内容。
抵抗线程错误并不是一种“品质”——它可以掩盖真正的线程错误,使它们更难重现和检测。
另请注意,可变类型与不可变类型实际上并不能保证线程安全。'Mutable' 可以在 ObjC 名称中使用以仅指代接口——不可变实例的内部实际上可能具有内部可变状态。简而言之,您不能假设具有可变子类的类型是线程安全的。
问题扩展:
假设有一个名为“name”的原子字符串属性,如果你从线程 A 调用 [self setName:@"A"],从线程 B 调用 [self setName:@"B"],从线程 B 调用 [self name]线程 C,则不同线程上的所有操作将串行执行,这意味着如果一个线程正在执行 setter 或 getter,则其他线程将等待。
如果所有线程同时尝试读取和/或写入该属性,则一次只有一个线程可以访问,如果该属性是原子的,则其他线程将被阻止。如果属性是非原子的,那么它们都将在同一“时间”对变量进行不受保护的读写访问。
如果另一个线程 D 同时调用 [name release],则此操作可能会导致崩溃,因为此处不涉及 setter/getter 调用。
正确的。
这意味着一个对象是读/写安全的(ATOMIC)但不是线程安全的,因为另一个线程可以同时向对象发送任何类型的消息。
嗯,还有很多。常见的例子是:
@interface MONPerson : NSObject
@property (copy) NSString * firstName;
@property (copy) NSString * lastName;
- (NSString *)fullName;
@end
原子的或非原子的,如果一个线程正在从该实例读取而另一个线程正在写入它,您将需要一种同步机制(例如锁定)。您最终可能会得到一个 MONPerson 的名字和另一个的姓氏——在 getter 的返回值甚至返回给您之前,该对象可能已经更改,或者可能发生以下情况:
线程 A:
p.firstName = @"Rob";
线程 B:
p.firstName = @"Robert";
线程 A:
label.string = p.firstName; // << uh, oh -- will be Robert
如果属性“name”是非原子的,那么上面示例中的所有线程 - A、B、C 和 D 将同时执行,产生任何不可预知的结果。
正确 - 最初的症状可能是引用计数不平衡(泄漏、过度释放)。
在原子的情况下,A、B 或 C 中的任何一个将首先执行,但 D 仍然可以并行执行。请对此发表评论......
正确的。但是如果你看一下上面的例子——单独的原子很少是锁的合适替代品。它必须看起来像这样:
线程 A:
[p lock]; // << wait for it… … … …
// Thread B now cannot access p
p.firstName = @"Rob";
NSString fullName = p.fullName;
[p unlock];
// Thread B can now access p
label.string = fullName;
线程 B:
[p lock]; // << wait for it… … … …
// Thread A now cannot access p
…
[p unlock];
原子访问器的平均速度比非原子访问器慢 20 倍。同样,如果您的类需要是线程安全的并且具有可变状态,那么当它在并发场景中运行时,您可能最终会使用锁。适当的锁定提供了您需要的所有保证——在这种情况下,原子访问器是多余的,使用原子只会增加 CPU 时间。常规锁的另一个好处是您拥有所需的所有粒度 - 尽管它通常比用于原子的自旋锁重,但您通常需要较少的获取,因此如果您正确使用常规锁,它最终会非常快.