【问题标题】:Is NSIndexPath threadsafe?NSIndexPath 是线程安全的吗?
【发布时间】:2012-03-02 20:41:49
【问题描述】:

Apple's multithreading docs 不要将NSIndexPath 列为线程安全的!作为一个不可变的类,我通常希望它是线程安全的。

以前,我确信用于声明 NSIndexPath 实例是共享且全局唯一的文档。不过现在这似乎已经消失了,这让我怀疑设计是针对 iOS5 / Mac OS X 10.7 进行了修改。

我在 Mac OS X 10.6 (Snow Leopard) 上看到了很多来自客户的崩溃报告,这些报告似乎在尝试访问索引路径时崩溃了。因此我想知道:实际实例线程安全吗,但是将它们从共享缓存中拉出的逻辑不是吗?有人有什么见解吗?

这是一个示例堆栈跟踪顺便说一句:

Dispatch queue: com.apple.root.default-priority
0 libobjc.A.dylib 0x96513f29 _cache_getImp + 9
1 libobjc.A.dylib 0x965158f0 class_respondsToSelector + 59
2 com.apple.CoreFoundation 0x948bcb49 ___forwarding___ + 761
3 com.apple.CoreFoundation 0x948bc7d2 _CF_forwarding_prep_0 + 50
4 com.apple.Foundation 0x994b10c5 -[NSIndexPath compare:] + 93
5 com.apple.Foundation 0x99415686 _NSCompareObject + 76
6 com.apple.CoreFoundation 0x948af61c __CFSimpleMergeSort + 236
7 com.apple.CoreFoundation 0x948af576 __CFSimpleMergeSort + 70
8 com.apple.CoreFoundation 0x948af38c CFSortIndexes + 252
9 com.apple.CoreFoundation 0x948fe80d CFMergeSortArray + 125
10 com.apple.Foundation 0x994153d3 _sortedObjectsUsingDescriptors + 639
11 com.apple.Foundation 0x994150d8 -[NSArray(NSKeyValueSorting) sortedArrayUsingDescriptors:] + 566

对我来说,这是一个 NSIndexPath 实例,它试图将自己与一个已释放的实例进行比较。

【问题讨论】:

  • 你如何处理这些索引路径,崩溃发生在哪里?多线程错误很神秘,NSIndexPath 的崩溃并不一定意味着问题出在NSIndexPath
  • 我执行一个获取请求,然后根据他们的-indexPath 方法对结果进行排序。在内部,每次调用该方法时,都会创建一个索引路径,该路径表示对象在树中的位置。我怀疑我收到了共享的NSIndexPaths,然后很快在另一个线程上被释放。
  • NSIndexPath 起源于哪里?它是获取对象的属性吗?
  • 我的托管对象上有一个-indexPath 方法。该方法按需计算路径,通过 +indexPathWithIndex:-indexPathByAddingIndex: 调用的组合创建它
  • 很高兴知道在单线程项目上使用 NSIndexPath 至少应该是安全的。因为我正在考虑自己使用它。否则,一个普通的 C 整数数组可以取代它的 (NSIndexPath) 位置。

标签: multithreading cocoa core-data foundation nsindexpath


【解决方案1】:

到目前为止,我的最佳答案是我怀疑的:

从 OS X 10.7 和 iOS 5 开始,NSIndexPath 是线程安全的。在此之前,instances 是线程安全的,因为它们是不可变的,但现有实例的共享检索不是。

对于按需返回索引路径的方法,我这样做了:

- (NSIndexPath *)indexPath;
{
    NSIndexPath *result = … // create the path as appropriate

    return [[result retain] autorelease];
}

自从实现最后一行代码后,我们就再也没有来自索引路径的崩溃报告了。

索引路径由-indexPathByAddingIndex:+indexPathWithIndex: 创建。

我看到的结果让我非常确定(在 10.7/iOS5 之前)这些方法正在返回一个现有的 NSIndexPath 实例。但是,当前线程不会以任何方式保留该实例,因此首先创建实例的线程(在我们的例子中是主线程)正在释放路径(可能通过弹出自动释放池)并为我们的工作线程留下一个悬空指针,如问题所示,使用时会崩溃。

这有点可怕,因为如果我的分析是正确的,我添加的 retain/autorelease 舞蹈只是用另一种不太可能的竞争条件替换一种竞争条件。

在 10.7/iOS5 之前,我只能想到一个真正的解决方法:将所有索引路径的创建限制在主线程中。如果这样的代码被大量调用,这可能会相当慢,因此可以通过维护自己的某种实例缓存以供后台线程使用来改进——以内存为代价。如果缓存保留了一个路径,那么你知道它不会被主线程释放。

【讨论】:

    【解决方案2】:

    Apple 没有明确将 NSIndexPath 列为线程安全的,但他们确实说不可变类通常是安全的,而可变类通常不是。由于 NSIndexPath 是不可变的,因此可以安全地假设它是线程安全的。

    但是“线程安全”并不意味着它不会在你在另一个线程上使用它之前被一个线程释放而导致崩溃。线程安全通常只是意味着它的 mutator 方法包​​含锁定以防止由于两个线程同时设置属性而导致的故障(这就是为什么没有 mutator 方法的类通常是线程安全的,尽管惰性 getter 和共享实例也会导致问题)。

    听起来您的错误更有可能是由于使用了自动释放池或其他机制导致您的对象在您无法控制的时间被释放。您可能应该确保所有并发访问的对象都存储在长寿命类的属性中,以便您可以控制它们的生命周期。

    创建一个自动释放的对象并在删除对其的所有强引用后从另一个线程访问它是一种危险的赛车游戏,无论相关对象是否“线程安全”,都可能导致难以追踪的崩溃”。

    【讨论】:

    • 是的,我了解多线程代码中的各种危险。我不是在一个线程上创建这些对象并期望它们继续存在于另一个线程上。我通过在工作线程上询问NSIndexPath 来创建对象。我的理解是,在 10.6 及更早版本上,NSIndexPath 实例是全局共享的。我怀疑这个全局缓存不是线程安全的,而不是单个实例。
    • 如果您下载旧文档集并搜索所有文档集,您仍然可以在 Xcode 的管理器中阅读 iOS 4.3 / OS 10.6 文档。我找到了您所指的行:“NSIndexPath 对象是唯一且共享的。如果包含指定索引或索引的索引路径已经存在,则返回该对象而不是新实例。”是的,它似乎已经在最新的文档中被删除了,所以也许你对 NSIndexPath 在 10.6 下不是线程安全的看法是正确的。
    猜你喜欢
    • 2021-10-12
    • 2015-04-18
    • 2011-10-07
    • 2011-10-28
    • 2023-03-14
    • 2016-08-24
    • 2011-08-16
    • 2011-09-28
    • 2010-12-14
    相关资源
    最近更新 更多