15

私の iPhone クライアントは、非同期要求に多く関与しており、多くの場合、辞書または配列の静的コレクションを一貫して変更しています。その結果、次のエラーでサーバーからの取得に時間がかかる大きなデータ構造がよく見られます。

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSCFArray: 0x3777c0> was mutated while being enumerated.'

これは通常、サーバーへの 2 つの要求が、同じコレクションを変更しようとしているデータと共に返されることを意味します。私が探しているのは、この有害なエラーを回避するためにコードを適切に構成する方法のチュートリアル/例/理解です。正しい答えはミューテックスだと思いますが、個人的にはまだ使用したことがありません。

これは、NSURLConnection を使用して非同期 HTTP 要求を作成し、要求が完了したら委任の手段として NSNotification Center を使用した結果です。同じコレクション セットを変更するリクエストを開始すると、これらの衝突が発生します。

4

6 に答える 6

28

これを行うにはいくつかの方法があります。あなたのケースで最も簡単なのは、おそらく @synchronized ディレクティブを使用することです。これにより、任意のオブジェクトをロックとして使用して、オンザフライでミューテックスを作成できます。

@synchronized(sStaticData) {
  // Do something with sStaticData
}

もう 1 つの方法は、NSLock クラスを使用することです。使用するロックを作成すると、mutex の取得に関して (ロックが使用できない場合のブロックなどに関して) 多少の柔軟性が得られます。

NSLock *lock = [[NSLock alloc] init];
// ... later ...
[lock lock];
// Do something with shared data
[lock unlock];
// Much later
[lock release], lock = nil;

これらのアプローチのいずれかを採用する場合は、NSMutableArray/Set/whatever をデータ ストアとして使用しているため、読み取りと書き込みの両方のロックを取得する必要があります。見てきたように、NSFastEnumeration は列挙されるオブジェクトの変更を禁止します。

しかし、ここでの別の問題は、マルチスレッド環境でのデータ構造の選択だと思います。複数のスレッドから辞書/配列にアクセスすることが厳密に必要ですか? それとも、バックグラウンド スレッドが受信したデータを結合して、データへのアクセスが許可されている唯一のスレッドであるメイン スレッドに渡すことができますか?

于 2009-02-16T19:39:53.387 に答える
15

データ (クラスを含む) が 2 つのスレッドから同時にアクセスされる可能性がある場合は、これらの同期を維持するための手順を実行する必要があります。

幸いなことに、Objective-C では、synchronized キーワードを使用してこれを非常に簡単に行うことができます。このキーワードは、任意の Objective-C オブジェクトを引数として取ります。同期セクションで同じオブジェクトを指定する他のスレッドは、最初のスレッドが終了するまで停止します。

-(void) doSomethingWith:(NSArray*)someArray
 {    
    // the synchronized keyword prevents two threads ever using the same variable
    @synchronized(someArray)
    {
       // modify array
    }
 }

複数の変数を保護する必要がある場合は、そのデータ セットへのアクセスを表すセマフォの使用を検討する必要があります。

// Get the semaphore.
id groupSemaphore = [Group semaphore];

@synchronized(groupSemaphore) 
{
    // Critical group code.
}
于 2009-02-16T19:43:34.673 に答える
0

sStaticData と NSLock の回答 (コメントは 600 文字に制限されています) に応じて、スレッド セーフな方法で sStaticData と NSLock オブジェクトを作成することに細心の注意を払う必要はありませんか (複数のロックが発生する可能性が非常に低いシナリオを回避するため)。異なるスレッドによって作成された)?

次の 2 つの回避策があると思います。

1) これらのオブジェクトが単一のルート スレッドで 1 日の開始時に作成されるように指定できます。

2) ロックとして使用するために、1 日の開始時に自動的に作成される静的オブジェクトを定義します。たとえば、静的 NSString をインラインで作成できます。

static NSString *sMyLock1 = @"Lock1";

それなら安心して使えると思います

@synchronized(sMyLock1) 
{
  // Stuff
}

そうしないと、スレッドセーフな方法でロックを作成することで、常に「鶏と卵」の状況に陥ると思いますか?

もちろん、ほとんどの iPhone アプリはシングル スレッドで実行されるため、これらの問題に遭遇する可能性はほとんどありません。

以前の [グループ セマフォ] の提案については知りませんが、それも解決策になる可能性があります。

于 2009-08-25T16:07:22.303 に答える
0

-fobjc-exceptionsNB 同期を使用している場合は、GCC フラグに追加することを忘れないでください。

Objective-C は、この記事と「例外処理」で説明されているスレッド同期と例外処理のサポートを提供します。これらの機能のサポートを有効にするには、GNU Compiler Collection (GCC) バージョン 3.3 以降の -fobjc-exceptions スイッチを使用します。

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html

于 2010-09-22T17:44:49.510 に答える
0

オブジェクトのコピーを使用して変更します。配列 (コレクション) の参照を変更しようとしているため、他の誰かがそれを変更する可能性がある (複数アクセス) ため、コピーを作成することができます。コピーを作成し、そのコピーを列挙します。

NSMutableArray *originalArray = @[@"A", @"B", @"C"];
NSMutableArray *arrayToEnumerate = [originalArray copy];

次に、arrayToEnumate を変更します。originalArray を参照しているのではなく、originalArray のコピーなので問題ありません。

于 2015-10-19T11:28:01.280 に答える
0

There are other ways if you don't want the overhead of Locking as it has its cost. Instead of using a lock to protect on shared resource (in your case it might be dictionary or array), you can create a queue to serialise the task that is accessing your critical section code. Queue doesn't take same amount of penalty as locks as it doesn't require trapping into the kernel to acquire mutex. simply put

 dispatch_async(serial_queue, ^{
    <#critical code#>
})

In case if you want current execution to wait until task complete, you can use

dispatch_sync(serial_queue Or concurrent, ^{
    <#critical code#>
})

Generally if execution doest need not to wait, asynchronous is a preferred way of doing.

于 2016-06-09T06:53:55.737 に答える