【问题标题】:Which is threadsafe atomic or non atomic?哪个是线程安全的原子或非原子?
【发布时间】:2012-09-10 07:42:37
【问题描述】:

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

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

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

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

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

【问题讨论】:

标签: objective-c multithreading cocoa thread-safety atomic


【解决方案1】:

对于 ObjC 属性 -- 两者都不是线程安全的

Atomic 更抵抗线程错误。总的来说,这是一个奇怪的默认设置。您喜欢原子的场景很少。原子可以增加正确性的概率,但它的级别太低,不能被认为是适当锁定机制的替代品。因此,如果您需要线程安全,您仍然需要在原子读/写之上的其他一些同步原语。如果您不需要线程安全(例如,实例是不可变的或仅打算从主线程运行),则 atomic 不会添加任何内容。

抵抗线程错误并不是一种“品质”——它可以掩盖真正的线程错误并使它们更难重现和检测。

另请注意,可变类型与不可变类型实际上并不能保证线程安全。 'Mutable' 可以在 ObjC 名称中使用以仅指代接口——不可变实例的内部实际上可能具有内部可变状态。简而言之,您不能假设具有可变子类的类型是线程安全的。


问题扩展:

假设有一个名为“name”的原子字符串属性,如果你从线程A调用[self setName:@"A"],从线程B调用[self setName:@"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 的 firstName 和另一个的 lastName -- 在 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 时间。常规锁的另一个好处是您拥有所需的所有粒度 - 尽管它通常比用于原子的自旋锁重,但您通常需要较少的获取,因此如果您正确使用常规锁,它最终会非常快.

【讨论】:

  • 感谢您如此详细地描述,这肯定会对我和其他可可未来的人有所帮助:)
【解决方案2】:

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

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

【讨论】:

  • 感谢 Jeroen 的回复。所以原子和非原子这两个都不是线程安全的?
  • 不,就像我的回答说的那样。它只是确保 getter 返回在多线程环境中有意义的值。
  • 没有收到一个完整的值意味着什么?我会看到什么样的错误?
【解决方案3】:

atomic 使执行以下线程安全。

self.myProperty = value;

id value = self.myProperty

它不会使以下线程安全

[myPorperty addObject:value];

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

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

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:
      1. Atomic 是线程安全的。
      2. 锁定为互斥体、自旋或另一种信号量的条件。 阅读更多 ... https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

      【讨论】:

        【解决方案6】:

        【讨论】:

        • 感谢 iris 的回复。我知道锁定和@synchronized,但我只是想确认哪个线程是安全的,为什么?
        • 正确实现时,原子是线程安全的,非原子不是线程安全的,因此速度更快,但与线程一起使用时则不然。
        • 添加锁定机制不会防止死锁。它只能创建它们。我认为“僵局”这个词可能并不像您认为的那样。使用“原子”访问器,访问本身在低级别上是线程安全的。但是,在更高级别上,您需要自己确保线程安全。在原始问题中,线程 A 和 B 设置一个变量,线程 C 读取它,这三个同时进行,非原子意味着操作可能会崩溃。原子意味着您不知道将存储什么以及 C 将读取什么,但它不会崩溃。
        【解决方案7】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-09-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-17
          • 2019-10-02
          相关资源
          最近更新 更多