首页 文章

Objective-C cpu缓存行为

提问于
浏览
10

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);
  });
}

在这种情况下,是否应该计数?

Update 1

Inter-thread Communication中的文档保证共享变量可用于线程间通信 .

在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块 .

在这种情况下,这不是必需的吗?这与Memory Barriers and Volatile Variables中的文档冲突:

但是,如果变量在另一个线程中可见,则此类优化可能会阻止另一个线程注意到对它的任何更改 . 将volatile关键字应用于变量会强制编译器在每次使用时从内存中加载该变量 .

所以我仍然不知道是否需要volatile,因为编译器可以使用寄存器缓存优化,或者如果不需要,因为编译器不知何故知道它是“共享”的东西 .

关于共享变量是什么或编译器如何知道它的文档不是很清楚 . 在上面的例子中,是否计算共享对象?假设count是一个int,那么它不是一个对象 . 它是共享的内存块还是仅适用于__block声明的变量?非块,非对象,非全局共享变量可能需要volatile .

Update 2

对于每个人来说,这是一个关于同步的问题,但事实并非如此 . 这是关于iOS平台上的CPU缓存行为 .

3 回答

  • 1

    最简单的方法,以及对开发人员大脑最不具挑战性的方法是在串行调度队列上执行任务 . 串行调度队列(如主队列)是多线程世界中的一个小型单线程岛 .

  • 0

    我知道你可能会询问跨线程使用变量的一般情况(在这种情况下,使用 volatile 和锁的规则对于ObjC和普通C的相同) . 但是,对于您发布的示例代码,规则略有不同 . (我将跳过并简化事情并使用Xcode来表示Xcode和编译器)

    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);
      });
    }
    

    我将假设self是一个 NSObject 子类,如下所示:

    @interface MyClass : NSObject {
        NSInteger something;
    }
    @property (nonatomic, assign) NSInteger count;
    @end
    

    Objective C是C的超集,如果你知道ObjC代码(有点,不完全)在编译之前被转换成C代码 . 所有 [self method:object] 调用都转换为 objc_msgSend(self, "method:", object) 调用,而 self 是一个带有ivars和其他运行时信息的C结构 .

    这意味着此代码没有达到预期的效果 .

    -(void)doThing{
       NSInteger results = something + self.count;
    }
    

    只是访问 something 不只是访问变量而是执行 self->something (这就是为什么在访问Objective C块中的ivar时需要获取对self的弱引用以避免保留周期) .

    第二点是Objective C属性并不存在 . self.count 变为 [self count]self.count = 5 变为 [self setCount:5] . Objective C属性只是语法糖;方便您节省一些打字,让事情看起来更好一些 .

    如果你还记得何时必须将 @synthesize propertyName = _ivarName 添加到 Headers 中声明的ObjC属性的 @implementation . (现在Xcode会自动为你做)

    @synthesize 是Xcode为您生成setter和getter方法的触发器 . (如果你还没写过 @synthesize Xcode希望你自己写一下setter和getter)

    // Auto generated code you never see unless you reverse engineer the compiled binary
    -(void)setCount:(NSInteger)count{
        _count = count;
    }
    -(NSInteger)count{
        return _count;
    }
    

    如果你担心使用 self.count 的线程问题,你会担心一次调用这些方法的2个线程(不是一次直接访问同一个变量,因为 self.count 实际上是方法调用而不是变量) .

    标头中的属性定义会更改生成的代码(除非您自己实现setter) .

    @property (nonatomic, retain)
    [_count release];
    [count retain];
    _count = count;
    
    @property (nonatomic, copy)
    [_count release];
    _count = [count copy];
    
    @property (nonatomic, assign)
    _count = count;
    

    TLDR

    如果你关心线程并且想要确保你没有在另一个线程上发生写入的一半时读取值,那么将 nonatomic 更改为 atomic (或者删除 nonatomic ,因为 atomic 是默认值) . 这会导致代码生成这样的东西 .

    @property (atomic, assign) NSInteger count;
    
    // setter
    @synchronized(self) {
        _count = count;
    }
    

    这赢得了't guarantee your code is thread safe, but (as long as you only access the property view it'的setter和getter)意味着你可以避免在另一个线程上写入时读取值的可能性 . 关于this question答案中有关原子和非物质的更多信息 .

  • 0

    您应该使用锁或其他一些同步机制来保护共享变量 . 据文件说:

    在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块 . 虽然共享变量快速而简单,但它们也比直接消息传递更脆弱 . 必须使用锁或小心地保护共享变量其他同步机制,以确保您的代码的正确性 . 如果不这样做可能会导致竞争条件,数据损坏或崩溃 .

    实际上,保护计数器变量的最佳方法是使用原子操作 . 你可以阅读这篇文章:https://www.mikeash.com/pyblog/friday-qa-2011-03-04-a-tour-of-osatomic.html

相关问题