【问题标题】:Is BOOL read/write atomic in Objective C?BOOL 在 Objective C 中是读/写原子的吗?
【发布时间】:2011-01-16 15:03:03
【问题描述】:

当两个线程“同时”将 BOOL 设置为 YES 时会发生什么?

【问题讨论】:

  • 它打开了通往另一个维度的虫洞。
  • 如果他们都将其设置为YES,那么无论写入是不是原子的,都不会有问题,可以吗?
  • @CarlNorum 这可能是真的,但对我来说原因并不明显

标签: objective-c atomic boolean


【解决方案1】:

这是Jacko建议的解决方案代码。
使用 volatile uint32_tOSAtomicOr32BarrierOSAtomicAnd32Barrier

#import <libkern/OSAtomic.h>

volatile uint32_t _IsRunning;

- (BOOL)isRunning {
    return _IsRunning != 0;
}

- (void)setIsRunning:(BOOL)allowed {

    if (allowed) {
        OSAtomicOr32Barrier(1, & _IsRunning); //Atomic bitwise OR of two 32-bit values with barrier
    } else {
        OSAtomicAnd32Barrier(0, & _IsRunning); //Atomic bitwise AND of two 32-bit values with barrier.
    }
}

【讨论】:

  • 酷!但是为什么不直接使用OSAtomicOr32Barrier(allowed, &amp; _IsRunning); 而不是条件语句呢?
  • 啊,没那么简单,抱歉。您不能使用“或”版本来重置标志,即您必须使用“和”版本来执行此操作(OSAtomicAnd32Barrier)。
【解决方案2】:

没有。如果没有锁定结构,在 Objective C 中读/写任何类型变量都不是原子的。

如果两个线程同时向 BOOL 写入 YES,则无论哪个线程先进入,结果都是 YES。

请看:Synchronizing Thread Execution

【讨论】:

  • 谢谢,米奇。如果一个线程将其设置为 YES,而另一个线程将其设置为 NO,是否会出现内存损坏?
  • developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/… 进行了非常彻底的讨论。我想我会使用 int 而不是 BOOL,并在这个 int 上使用 OSAtomic 操作。
  • 您的问题的含义是是否可能会压缩其他一些内存块。不;那不可能发生。最糟糕的情况是,您在另一个线程中写了“是”之后,您似乎会读到“否”。
  • 谢谢。我决定改用 int32_t 而不是 BOOL,并使用 OSAtomicAnd32Barrier 和 OSAtomicOr32Barrier 来设置和取消设置我的标志。
  • 小麦先生,您是否愿意详细说明为什么单字节 POD(例如 BOOL)即使声明为非原子 POD 也可能是非原子的?这是 google 搜索的“arm store single byte vs word”heyrick.co.uk/assembler/str.html -> 有专门的指令用于从内存 LDRB 和 STRB 存储和加载单个字节
【解决方案3】:

我将不得不偏离接受的答案。对不起。 虽然目标 c 不保证声明为非原子的 BOOL 属性 实际上是原子的我不得不猜测你最喜欢的硬件 关心(所有 iOS 和 macOS 设备)有指令以原子方式执行字节读取和存储。所以,除非 Apple 推出 Road Light OS 运行 在具有 5 位宽总线的 IBM 微控制器上发送 10 位字节 在需要原子 BOOL 的情况下,您也可以使用非原子 BOOL。该代码不能移植到 Road Light OS,但如果可以的话 对于这个用例来说,牺牲非原子代码的未来证明是可以的。 我敢肯定 s.o 上有一些顽固的人。这将带来为原子/非原子情况分解合成的 BOOL getter 和 setter 以查看有什么区别的挑战。至少在 ARM 上。

您从中得出的结论可能是这样的

  1. 您可以将 BOOL 属性声明为原子属性,并且不会花费您一毛钱 在所有硬件 iOS 和 macOS 上本质上支持。
  2. 内存屏障与原子性正交
  3. 您绝对不应该使用 4 字节属性将布尔值存储到 除非你进入 [非常] 模糊逻辑。 这是愚蠢和浪费,你不想成为Java程序员的克隆, 谁不能区分浮点数和双精度数?
  4. BOOL 变量(显然不支持原子/非原子装饰器 在某些狭窄的总线架构上不会是原子的,目标 C 会 无论如何都不能使用(我想有或没有一些[非常]微型操作系统的微控制器是C和汇编领域。他们通常不需要行李 objc 运行时会带来)

【讨论】:

    【解决方案4】:

    当两个线程“同时”将 BOOL 设置为 YES 时会发生什么?

    那么它的值就是YES。如果线程将相同的值写入相同的内存位置,则内存位置将具有该值,无论它是否是原子的都不起作用。只有当两个线程将不同的值写入同一个内存位置,或者一个线程写入它而另一个线程正在读取它时,它才会起作用。

    Objective C 中的 BOOL 读/写是原子的吗?

    如果您的硬件是运行 macOS 的 Macintosh。 BOOL 在 PPC 系统上是 uint32_t,在 Intel 系统上是 char,编写这些数据类型在它们各自的系统上是原子的。

    但是,Obj-C 语言没有这样的保证。在其他系统上,这取决于您使用的编译器以及如何为该平台定义BOOL。大多数编译器(gcc、clang、...)保证写入int-size 的变量始终是原子的,其他大小是否原子取决于CPU。

    请注意,原子与线程安全不同。写BOOL 不是内存屏障。编译器和 CPU 可能会围绕 BOOL 对指令重新排序:

    a = 10;
    b = YES;
    c = 20;
    

    无法保证指令按该顺序执行。 bYES 的事实并不意味着 a 是 10。编译器和 CPU 可以根据需要随意调整这三个指令,因为它们不相互依赖。显式原子指令以及锁、互斥体和信号量通常是内存屏障,这意味着它们指示编译器和 CPU 不要将位于该操作之前的指令移到它之外,也不要将位于该操作之后的指令移到它之前(这是一个硬边界,该指令可能不会通过)。

    也不保证缓存一致性。即使您将BOOL 设置为YES,其他一些线程仍可能在有限的时间内将其视为NO。内存屏障操作通常也是确保系统中所有线程/内核/CPU之间缓存同步的操作。

    并且在这里添加一些真正有用的东西,这是您如何确保设置布尔值是原子的,并在 2020 年使用 C11 充当内存屏障,这也将在 Obj-C 代码中工作:

    #import <stdatomic.h>
    
    // ...
    
    volatile atomic_bool b = true;
    
    // ...
    
    atomic_store(&b, true);
    
    // ...
    
    atomic_store(&b, false);
    

    此代码不仅可以保证对 bool 进行原子写入(系统将为其选择适当的类型),它还将充当内存屏障(顺序一致)。

    要从另一个线程以原子方式读取布尔值,您可以使用

    bool x = atomic_load(&b);
    

    您还可以使用atomic_load_explicitatomic_store_explicit 并传递显式内存顺序,这使您可以更细粒度地控制允许哪些内存重新排序,哪些不允许。

    在此处详细了解您的可能性:

    http://llvm.org/docs/Atomics.html

    请务必阅读“优化器注意事项”以查看允许哪些内存重新排序。如果有疑问,请始终使用顺序一致(memory_order_seq_cst,如果未指定,则为默认值)。它不会带来最快的性能,但它是最安全的选择,而且你真的应该只在你知道自己在做什么的情况下使用其他东西。

    【讨论】:

      猜你喜欢
      • 2010-09-08
      • 2010-09-08
      • 1970-01-01
      • 2016-07-08
      • 2010-12-09
      • 2011-10-04
      • 2011-10-23
      • 1970-01-01
      相关资源
      最近更新 更多