まず、Apple Threading Programming Guide の章を引用させてください。
コードの正確性に対する脅威に注意する
ロックとメモリ バリアを使用する場合は、コード内での配置を常に慎重に検討する必要があります。適切に配置されているように見えるロックでさえ、実際には誤った安心感に陥ることがあります。次の一連の例では、一見無害に見えるコードの欠陥を指摘することで、この問題を説明しようとしています。基本的な前提は、一連の不変オブジェクトを含む可変配列があることです。配列内の最初のオブジェクトのメソッドを呼び出したいとします。次のコードを使用してこれを行うことができます。
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[arrayLock unlock];
[anObject doSomething];
配列は変更可能であるため、目的のオブジェクトを取得するまで、配列をロックすることにより、他のスレッドが配列を変更することを防ぎます。また、取得するオブジェクト自体は不変であるため、doSomething メソッドの呼び出しの周りにロックは必要ありません。
ただし、前の例には問題があります。ロックを解放し、doSomething メソッドを実行する前に別のスレッドが入ってきて、配列からすべてのオブジェクトを削除するとどうなりますか? ガベージ コレクションのないアプリケーションでは、コードが保持しているオブジェクトが解放され、anObject が無効なメモリ アドレスを指している可能性があります。この問題を解決するには、次のように、既存のコードを単純に再配置し、doSomething の呼び出し後にロックを解放することを決定する場合があります。
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject doSomething];
[arrayLock unlock];
doSomething 呼び出しをロック内に移動することにより、コードは、メソッドが呼び出されたときにオブジェクトがまだ有効であることを保証します。残念ながら、doSomething メソッドの実行に時間がかかると、コードが長時間ロックを保持することになり、パフォーマンスのボトルネックが生じる可能性があります。
コードの問題は、クリティカル領域の定義が不十分だったことではなく、実際の問題が理解されていなかったことです。実際の問題は、他のスレッドの存在によってのみ引き起こされるメモリ管理の問題です。別のスレッドによって解放される可能性があるため、ロックを解放する前に anObject を保持することをお勧めします。このソリューションは、オブジェクトが解放されるという実際の問題に対処し、潜在的なパフォーマンスの低下を招くことなく対処します。
NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject retain];
[arrayLock unlock];
[anObject doSomething];
[anObject release];
問題は、ARC の使用中に問題を解決する方法はありますか?