33

我搜索并发现不可变是线程安全的,而可变不是。这可以。但是我得到了误导性的注释、博客、关于线程安全的原子与非原子的答案,请给出答案的解释。

假设有一个名为“name”的原子字符串属性,如果你[self setName:@"A"]从线程 A 调用[self setName:@"B"],从线程 B 调用[self name],从线程 C 调用,那么不同线程上的所有操作都将串行执行,这意味着如果一个线程正在执行 setter 或getter,然后其他线程将等待。这使得属性“name”读/写安全,但如果另一个线程 D[name release]同时调用,则此操作可能会产生崩溃,因为这里不涉及 setter/getter 调用。这意味着一个对象是读/写安全的(ATOMIC)但不是线程安全的,因为另一个线程可以同时向对象发送任何类型的消息。

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

您对此的评论将帮助我们......

我的问题是,“在可可中哪个是线程安全的,原子的还是非原子的?”

4

7 回答 7

69

对于 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 时间。常规锁的另一个好处是您拥有所需的所有粒度 - 尽管它通常比用于原子的自旋锁重,但您通常需要较少的获取,因此如果您正确使用常规锁,它最终会非常快.

于 2012-09-10T08:48:13.093 回答
14

atomic 保证对变量的原子访问,但它不会使您的代码线程安全。非原子的也不行。

使用“原子”,合成的 setter/getter 方法将确保始终从 getter 返回整个值或由 setter 设置,而不管任何其他线程上的 setter 活动。因此,如果线程 A 在 getter 的中间,而线程 B 调用 setter,则实际可行的值将返回给 A 中的调用者。对于非原子,您没有这样的保证。

于 2012-09-10T07:44:44.197 回答
9

atomic 使执行以下线程安全。

self.myProperty = value;

或者

id value = self.myProperty

它不会使以下线程安全

[myPorperty addObject:value];

Atomic 使设置或获取属性成为线程安全的,但它不会使调用该属性本身的任何方法成为线程安全的。

设置或获取值可能需要多条 CPU 指令,因此这意味着设置或获取可以中途中断,另一个线程可以做一些事情,使前一个线程在设置或获取值时的进度无效。

atomic 说以某种方式设置或获取值,以便它发生就像它发生在一个不可分割的指令中一样,因此没有其他线程可以半途而废并搞砸事情。

不可变对象是线程安全的,很简单,因为您无法更改它们,因为您可以从一个不可变对象更改另一个属性,因此除非您将其设为原子,否则该部分不会是线程安全的。

于 2012-09-10T11:38:17.243 回答
2

还有一个没有提到的“原子”属性,它是 ARC 之前线程安全所必需的(可能仍然是)。首先解释为什么需要它:假设没有 ARC,您读取一个对象属性并立即保留它。但是另一个线程可能会在您读取属性和保留调用之间出现,将对象属性设置为 nil,从而导致对象被释放。您将保留发送到已释放的对象,这是不健康的。这将是一个非常罕见的错误,因为它只在时机恰到好处时才会发生。为了防止这种情况,原子对象属性总是返回自动释放的对象。

于 2014-02-24T10:12:04.000 回答
0
  1. 原子是线程安全的。
  2. 锁定为互斥量、自旋或条件,这是另一种类型的信号量。阅读更多... https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html
于 2016-05-04T14:02:57.783 回答
-1

您应该考虑实施锁定机制,这样您的应用程序就不会出现死锁。此外,如果您正在实现核心数据,您应该阅读 iOS 线程安全。

看。

http://developer.apple.com/library/iOS/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

http://developer.apple.com/library/iOS/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html%23//apple_ref/doc/uid/10000057i-CH8-SW1

于 2012-09-10T08:37:02.137 回答
-8

非原子是线程安全的。保证获取变量的值。

于 2015-03-09T06:29:48.600 回答