【发布时间】:2017-07-09 04:03:50
【问题描述】:
Apple 提供了一些关于同步变量甚至执行顺序的文档。我没有看到任何关于 CPU 缓存行为的文档。 Objective-C 开发者有什么保证和控制来确保线程之间的缓存一致性?
考虑在后台线程上设置变量但在主线程上读取的以下情况:
self.count = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
self.count = 5;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%i", self.count);
});
}
在这种情况下 count 应该是 volatile 吗?
更新 1
Inter-thread Communication 中的文档保证共享变量可用于线程间通信。
另一个在两个线程之间传递信息的简单方法是使用全局变量、共享对象或共享内存块。
这是否意味着在这种情况下不需要 volatile?这与Memory Barriers and Volatile Variables 中的文档冲突:
如果变量在另一个线程中是可见的,那么这样的优化可能会阻止另一个线程注意到它的任何变化。将 volatile 关键字应用于变量会强制编译器在每次使用该变量时从内存中加载该变量。
所以我仍然不知道是否需要 volatile 是因为编译器可以使用寄存器缓存优化,还是不需要它,因为编译器不知何故知道它是“共享”的东西。
文档对共享变量是什么或编译器如何知道它不是很清楚。在上面的例子中,count 是一个共享对象吗?假设 count 是一个 int,那么它就不是一个对象。它是共享内存块还是仅适用于 __block 声明的变量?非块、非对象、非全局、共享变量可能需要 volatile。
更新 2
对于所有认为这是关于同步的问题的人来说,事实并非如此。这是关于 iOS 平台上的 CPU 缓存行为。
【问题讨论】:
-
tl;dr: 原子属性保证跨线程和 CPU 缓存的原子访问,但要小心,因为这并不一定意味着线程安全。例如,
self.count += 1不是原子操作(self.count += 1===self.count = self.count + 1;读取和写入之间可能发生任何事情)。 -
据我了解,在 Cocoa 中实现的 atomic 确实可以确保跨处理器的一致性,但不能保证。
-
顺便说一句,
volatile并不能确保您将读取未缓存的值:stackoverflow.com/questions/558848/…、stackoverflow.com/questions/18695120/…、stackoverflow.com/questions/7872175/…(它只保证编译器不会优化读取。) -
Memory Barriers and Volatile Variables "但是,如果变量在另一个线程中可见,那么这样的优化可能会阻止其他线程注意到它的任何变化。将 volatile 关键字应用于变量会强制编译器加载该变量每次使用时都会从内存中更改。”这要么意味着 volatile 修复了缓存问题,要么这是非常具有误导性和不完整的文档。
-
我一点也不擅长 Objective C。但是从架构的角度来看这个问题,如果你用锁或原子块保护读操作(主线程)和写操作(后台线程),那么可以保证主线程会看到来自后台线程。仅仅是因为,一次只有一个会访问 count 变量,当另一个尝试访问它时,一致性协议将启动并传递更新的值。
标签: ios objective-c multithreading caching cpu-cache