【问题标题】:Atomic properties vs thread-safe in Objective-CObjective-C 中的原子属性与线程安全
【发布时间】:2014-02-01 14:58:47
【问题描述】:

在我读过的大多数讨论中,它表明使属性原子化并不能保证它是线程安全的,它只是保证返回的值不会因为一个对象写入而成为垃圾它和另一个试图同时阅读它。

我知道这不是线程安全的,因为第三个对象可能正在写入它,虽然访问它的对象不会回收垃圾,但由于多个对象正在写入它,它并不完全确定它将返回哪个值同时,它可能会得到它们的任何值。

所以当我们说它不会返回垃圾时,垃圾是指如果一个对象是非原子的并且一个对象试图访问它而另一个对象正在写入它,它可能会在中间返回结果 -写,并且只得到写带来的变化的部分、不完整的版本?这就是“垃圾”在这个意义上的含义吗?哪些原子属性有助于防止?

【问题讨论】:

  • 关于“线程安全”的事情是,没有什么本身是“线程安全的”。 “线程安全”只能在给定上下文中真正确定,其中“线程安全”意味着尽管有任何可能的多线程,但可以确保整个上下文的正确操作。例如,可变数组的特定实现可以说是真空中的“线程安全”,因为永远不会访问内部不一致的数据,但如果它的条目没有与其引用的外部数据保持同步,那将毫无意义。而且这种同步只能在外部维持。
  • @doug-smith 您可能会发现我对这个问题的回答 + 后面的 cmets 很有帮助。 stackoverflow.com/questions/588866/…

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


【解决方案1】:

Objective C 中的atomic 属性保证您永远不会看到部分写入。 当 @property 具有属性 atomic 时,不可能只写入部分值。 setter 是这样的:

- (void)setProp:(NSString *)newValue {
    [_prop lock];
    _prop = newValue;
    [_prop unlock];
}

所以如果两个线程要同时写入值@"test"@"otherTest",那么 在任何给定时间,该属性只能是该属性的初始值或@"test"@"otherTest"nonatomic 更快,但该值是垃圾值,没有 @"test"/@"otherTest" (thx @Gavin) 或任何其他垃圾值的部分字符串。

但是 atomic 只是简单使用的线程安全。 这不是保证。 Appledoc 说:

考虑一个XYZPerson 对象,其中一个人的第一个和最后一个 使用来自一个线程的原子访问器更改名称。如果另一个 线程同时访问两个名称,原子getter方法 将返回完整的字符串(不会崩溃),但没有 保证这些值将是相对于每个值的正确名称 其他。如果在更改之前访问了名字,但最后一个 更改后访问名称,您将得到不一致的结果, 一对不匹配的名称。

我在使用 atomic 时从来没有遇到过问题。我是这样设计代码的,原子属性没有问题。

【讨论】:

  • 你永远不会看到像“teerTest”这样的东西,因为将属性设置为字符串只是设置指向Objective-C对象的指针。因此,您可能会看到一个垃圾指针,但绝不是您尝试设置的两个字符串的混合体(除非它碰巧是在其他地方定义的字符串,就像您实际上在某个地方有一个字符串“teerTest”)应用程序,并且垃圾指针实际上设法指向它)。
  • 是的,你是对的。我写了“说清楚”,但这样更令人困惑。我改了,谢谢提醒。
  • 我从 Apple 文档中了解到的是:如果您有两个原子属性并且您需要在线程以任何方式访问或修改它们时从这两个属性中生成结果数据,那么您将获得结果(保证)但可能不正确,我觉得这已经足够了,而不是手动编写线程安全代码。
  • 对我来说,this article about atomic and nonatomic operations at the processor level 对于了解如何可能会部分写入值特别有趣。
  • 我认为你说“你不会看到任何部分写入”是误导性的,好像非原子会提供这样的(你最初认为这样并且只修复了问题是粗体的,但没有修复其他不太重要的部分):]。恕我直言,我认为您的整个答案需要从头开始重新构建。此外,我认为你的回答有点误导,因为你已经驳斥了部分书面场景,而如果有一次写作尝试已经完成一半并且正在进行阅读,那么这是一种可能性。 (这种情况与2次同时尝试的情况不同)
【解决方案2】:

回答您的第三段;基本上是的。线程正在写入原子序数时无法读取原子序数。

例如,如果一个线程已写入原子四字节数的前两个字节,并且在另一个线程上请求读取该数字,则该读取必须等到所有四个字节都已写入。

相反,如果一个线程已经写入了一个非原子四字节数字的前两个字节,并且此时另一个线程请求读取该数字,它将读取前两个新数据字节,但不会从其他两个字节中的先前写入操作中获取旧数据。

【讨论】:

  • 反过来是真的吗?如果 4 个字节未声明为非原子字节,一个线程是否可以看到 2 个写入的字节,而另一个线程正在写入 4 个字节?
  • @danh:太多的双重否定让我无法解析你的问题。我已经编辑了我的答案以澄清。
  • :-) 我想不出非原子的另一种表达方式。
  • 究竟哪些类型是自然原子的取决于平台特定的。在 x86 的情况下,至少应该是 4 字节整数。结构或双打不会。
  • 请注意,单字节值甚至存在问题。在处理器中,代码 a++ 从内存中读取值,将其加 1,然后将其写回内存(读/修改/写)。如果没有原子限定符或某种其他形式的锁定,则另一个线程可能会将值写入读取和写入之间的“a”位置,并且该值将丢失。这类错误很难找到,因为它们本质上基本上是不确定的。原子限定符不是灵丹妙药。您确实需要了解并发性。
【解决方案3】:

罗伯特·哈维的回答是正确的,但考虑到人们经常错过的一个子案例。考虑以下代码:http://pastebin.com/S7XyJm6G

除了阻止您读取部分写入的值外,原子属性还阻止您获取您无法控制其生命周期的对象(它们通过保留然后自动释放对象来做到这一点)。这在像我链接的示例这样的单线程代码中很重要,但在多线程代码中更重要,因为另一个线程可能导致对象从您的下方释放出来。

【讨论】:

  • 您能否举一个 iOS 应用程序的示例,其中使用了线程安全。假设我正在使用 Instagram,我尝试异步下载我的图像,我会遇到什么问题?它只是一个设备,一个用户,一个主线程指挥其他一切,确切的 假设的 属性是它可以写​​两次同时 并且需要我遵循线程安全设计?
  • 如果你异步下载它们,主线程并不能控制一切。在您描述的情况下,您最有可能遇到的不是多个同时写入,而是同时读取(来自主线程)和写入(来自下载线程)。但是,在该示例中,原子属性可能不是您用来实现线程安全的工具;还有许多其他方法。
【解决方案4】:

在并发编程中:

原子的意思是如果在某个线程(线程#1)和其他线程(线程#2)中为写入操作访问的属性值尝试访问原子值以进行读取或写入操作,然后其他线程(线程#2)等到线程#1 完成它的任务。 换句话说,原子以先到先得的方式同步属性的访问。

非原子意味着如果在某个线程(线程#1)和其他线程(线程#2)中为写入操作访问的属性值尝试访问非原子值以进行读取或写入操作,那么其他线程(线程# 2) 获取值立即获取旧值

【讨论】:

    【解决方案5】:

    显式实现

    @property(原子,保留)NSNumber *count

    会是这样的

    - (NSNumber *)count {
        NSNumber *count;
        @synchronized(self) {
            count = [_count retain]; // +1
        }
        return [count autorelease]; // delayed -1
    }
    
    - (void)setCount:(NSNumber *)count {
        id oldValue;
        @synchronized(self) {
            oldValue = _count;
            _count = [count retain];
        }
        [oldValue release];
    }
    

    原子是属性的默认行为。原子属性在获取或设置值时增加了线程安全级别。也就是说,无论其他线程在做什么,属性的 getter 和 setter 都将始终完全完成。这些属性的访问速度会比非原子等价物慢一些。

    我们会明确地实现

    @property(非原子,保留)NSNumber *count

    喜欢这个

    - (NSNumber *)count {
        return _count;
    }
    
    - (void)setCount:(NSNumber *)count {
        if (count != _count) {
            id oldValue = _count;
            _count = [count retain];
            [_oldValue release];
        }
    }
    

    非原子属性不是线程安全的,会直接返回它们的属性。这将比原子属性更快,但如果不采取预防措施显然会带来一些风险。

    这些非原子属性的设置器和获取器

    【讨论】:

      猜你喜欢
      • 2019-10-02
      • 2017-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多