从不同线程访问只读对象的想法

时间:2013-11-13 17:21:12

标签: objective-c multithreading thread-safety nsformatter

基于我之前在SO中的讨论(参见Doubts on concurrency with objects that can be used multiple times like formatters),这里我要问一个关于在应用程序生命周期中创建一次(并且从未修改过,因此只读)的对象的更多理论问题并且可以从不同的线程访问它们。一个简单的用例是核心数据。 Formatters可以在不同的线程中使用(主线程,导入线程等)。

例如,

NSFormatter创建起来非常昂贵。基于此,他们可以创建一次然后重用。可以遵循的典型模式(NSFormatter文章中的@mattt也突出显示)如下。

+ (NSNumberFormatter *)numberFormatter {
    static NSNumberFormatter *_numberFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _numberFormatter = [[NSNumberFormatter alloc] init];
        [_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    });

    return _numberFormatter;
}

即使我确定这是一个非常好的方法(创建了一种只读/不可变对象),格式化程序也不是线程安全的,因此以线程安全的方式使用它们可能会很危险。我在NSDateFormatter crashes when used from different threads中找到了关于论点的讨论,其中作者注意到可能发生崩溃。

  

NSDateFormatters不是线程安全的;有一个背景线程   试图同时使用相同的格式化程序(因此   随机性)。

那么,从不同线程访问格式化程序可能会出现什么问题?是否有任何安全模式?

2 个答案:

答案 0 :(得分:7)

格式化程序的具体答案:

在iOS 7 / OSX 10.9之前,即使是对格式化程序的只读访问也不是线程安全的。 ICU在响应请求时有大量的惰性计算,如果同时进行,可能会崩溃或产生不正确的结果。

在iOS 7 / OSX 10.9中,NSDateFormatterNSNumberFormatter在内部使用锁来序列化对底层ICU代码的访问,从而防止出现此问题。

一般答案:

真正的访问/不可变对象确实通常是线程安全的,但是很难不可能分辨出哪些内部实际上是不可变的,哪些只是向外界呈现一个不可变的接口。

在您自己的代码中,您可以知道这一点。在使用其他人的课程时,您必须依赖他们所记录的有关如何安全使用课程的内容。

(编辑,因为要求序列化访问格式化程序的示例)

// in the dispatch_once where you create the formatter
dispatch_queue_t formatterQueue = dispatch_queue_create("date formatter queue", 0);

// where you use the formatter
dispatch_sync(formatterQueue, ^{ (do whatever you wanted to do with the formatter) });

答案 1 :(得分:3)

  

我在问一个关于在应用程序生命周期中创建一次(并且从未修改过,因此只读)的对象的更多理论问题

这在技术上并不正确:为了使对象真正是只读的,它也必须是不可变的。例如,可以创建NSMutableArray一次,但由于该对象允许在创建后进行更改,因此不能将其视为只读,因此在没有其他同步的情况下并发使用是不安全的。

此外,逻辑上不可变对象可能具有变异实现,使它们不是线程安全的。例如,在第一次使用时执行延迟初始化的对象,或者在没有同步的情况下在其实例变量中执行缓存状态,这些对象不是线程安全的。似乎NSDateFormatter就是这样一个类:即使你没有调用方法来改变类的状态,你似乎也会崩溃。

对此的一个解决方案可能是使用thread-local storage:而不是每个应用程序创建一个NSDateFormatter,而不是每个线程创建一个,但这样可以节省一些CPU周期:{{3提到他们通过使用这个简单的技巧设法减少了启动时间的5..10%。