NSIndexPath是线程安全的吗?

16

苹果的多线程文档没有列出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实例试图将自己与已释放的实例进行比较。


1
你如何处理这些索引路径,崩溃发生在哪里?多线程错误很神秘,使用 NSIndexPath 引起的崩溃并不一定意味着问题出在 NSIndexPath 上。 - hamstergene
NSIndexPath是从哪里来的?它是获取对象的属性吗? - Max MacLeod
我的托管对象上有一个“-indexPath”方法。该方法按需计算路径,通过使用“+indexPathWithIndex:”和“-indexPathByAddingIndex:”调用的组合来创建它。 - Mike Abdullah
你在单线程项目中是否曾经遇到过无法令人满意地解决和解释的indexPath问题? - Stanley
1
很高兴知道在单线程项目中使用NSIndexPath应该是安全的。因为我正在考虑自己使用它。否则,一个普通的C整数数组可以取代它(NSIndexPath)。 - Stanley
显示剩余2条评论
2个回答

4

目前我最好的答案是我怀疑:

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

对于我的按需返回索引路径的方法,我做了以下操作:

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

    return [[result retain] autorelease];
}

自从实施了最后一行代码以来,我们再也没有从索引路径收到崩溃报告。索引路径是由-indexPathByAddingIndex:+indexPathWithIndex:创建的。
我看到的结果让我相信,在10.7 / iOS5之前,这些方法返回一个现有的NSIndexPath实例。然而,当前线程没有以任何方式保留该实例,因此第一次创建实例的线程(在我们的情况下为主线程)会释放该路径(可能通过弹出自动释放池),并留下悬空指针,当使用时会导致崩溃,如问题中所见。
这有点可怕,因为如果我的分析正确,我添加的retain/autorelease操作只是将一个竞争条件替换为另一个不太可能的竞争条件。
在10.7 / iOS5之前,我只能想到一个真正的解决方法:将所有索引路径的创建限制在主线程中。如果这样的代码被频繁调用,那么可能会变得非常缓慢,因此可以通过维护自己的实例缓存来改进 - 以内存为代价 - 供后台线程使用。如果缓存保留路径,则您知道主线程不会将其取消分配。

1

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

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

听起来你的bug更可能是由于使用autorelease池或其他机制导致对象在你控制范围之外的时间被释放。您应该确保任何同时访问的对象都存储在长寿命类的属性中,以便您可以控制它们的生命周期。

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


是的,我了解多线程代码中的各种危险。我不会在一个线程上创建这些对象,并期望它们在另一个线程上保持活动状态。我是通过在工作线程上请求NSIndexPath来创建这些对象的。我的理解是,在10.6及更早版本中,NSIndexPath实例是全局共享的。我怀疑这个全局缓存不是线程安全的,而是单个实例。 - Mike Abdullah
1
如果您下载旧的文档集并搜索所有文档集,则仍然可以在Xcode的组织者中阅读iOS 4.3 / OS 10.6文档。我已经找到了您所指的行:“NSIndexPath对象是唯一的并且共享的。如果包含指定索引或索引的索引路径已经存在,则返回该对象而不是新实例。”是的,似乎在最新的文档中已将其删除,因此也许您关于NSIndexPath在10.6下不是线程安全的是正确的。 - Nick Lockwood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接