BOOL是否在Objective C中读/写原子?

Jac*_*cko 9 boolean atomic objective-c

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

Par*_*fna 7

以下是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.
    }
}
Run Code Online (Sandbox Code Playgroud)


Mit*_*eat 6

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

如果两个线程同时向BOOL写入YES,则结果为YES,无论哪个线程首先进入.

请参阅:同步线程执行

  • 谢谢,米奇.如果一个线程将其设置为YES而另一个线程将其设置为NO,那么内存是否会损坏? (2认同)

Ant*_*hko 5

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

您从中得出的结论可能是

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


Mec*_*cki 5

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

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

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

如果您的硬件是运行 macOS 的 Macintosh,则适用。BOOLuint32_tPPC 系统和charIntel 系统上,写入这些数据类型在各自的系统上是原子的。

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

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

a = 10;
b = YES;
c = 20;
Run Code Online (Sandbox Code Playgroud)

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

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

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

#import <stdatomic.h>

// ...

volatile atomic_bool b = true;

// ...

atomic_store(&b, true);

// ...

atomic_store(&b, false);
Run Code Online (Sandbox Code Playgroud)

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

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

bool x = atomic_load(&b);
Run Code Online (Sandbox Code Playgroud)

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

在这里详细了解您的可能性:

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

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