首先让我引用 Apple Threading Programming Guide 中的一章:
注意对代码正确性的威胁
使用锁和内存屏障时,您应该始终仔细考虑它们在代码中的位置。即使是看起来位置不错的锁,实际上也会让你产生一种虚假的安全感。以下一系列示例试图通过指出看似无害的代码中的缺陷来说明这个问题。基本前提是你有一个包含一组不可变对象的可变数组。假设您要调用数组中第一个对象的方法。您可以使用以下代码执行此操作:
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[arrayLock unlock];
[anObject doSomething];
因为数组是可变的,所以数组周围的锁会阻止其他线程修改数组,直到你得到想要的对象。并且因为您检索的对象本身是不可变的,所以在调用 doSomething 方法时不需要锁定。
但是,前面的示例存在问题。如果你释放了锁,另一个线程进来并在你有机会执行 doSomething 方法之前从数组中删除了所有对象,会发生什么?在没有垃圾收集的应用程序中,您的代码持有的对象可能会被释放,从而使对象指向无效的内存地址。要解决此问题,您可能决定简单地重新排列现有代码并在调用 doSomething 后释放锁定,如下所示:
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject doSomething];
[arrayLock unlock];
通过在锁内移动 doSomething 调用,您的代码可以保证在调用该方法时对象仍然有效。不幸的是,如果 doSomething 方法需要很长时间才能执行,这可能会导致您的代码长时间持有锁,这可能会造成性能瓶颈。
代码的问题不是关键区域定义不好,而是实际问题没有被理解。真正的问题是仅由其他线程的存在触发的内存管理问题。因为它可以被另一个线程释放,更好的解决方案是在释放锁之前保留一个对象。该解决方案解决了对象被释放的真正问题,并且不会引入潜在的性能损失。
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject retain];
[arrayLock unlock];
[anObject doSomething];
[anObject release];
问题是:在使用 ARC 时有什么办法可以解决这个问题吗?