3

質問: Grand Central Dispatch (GCD) を使用してデータを (プリミティブを超えて) バックグラウンド タスクに渡すための推奨/ベスト/受け入れられている方法は何ですか?

目的の C ブロックで気になる点は次のとおりです。ブロックによってアクセスされる変数は、ブロックが後でアクセスできるように、ヒープ上のブロック データ構造にコピーされます。コピーされたポインター参照は、複数のスレッドが同じオブジェクトにアクセスしていることを意味する場合があります。

私はまだ目的の C と iOS にかなり慣れていませんが、新しいスレッド (C++、Java、C、C#) ではありません。

コード セット #1 (スコープからのプリミティブ コピー)

//Primitive int
int taskIdBlock = self->taskNumber;

//declare a block that takes in an ID and sleep time.
void (^runTask)(int taskId, int sleepTime);

//Create and assign the block
runTask = ^void(int taskId, int sleepTime)
{
    NSLog(@"Running Task: %d", taskId);
    // wait for x seconds before completing this method
    [NSThread sleepForTimeInterval:sleepTime];

    //update the main UI
    //tell the main thread we are finished with this task.
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       NSLog(@"Completed Task %d",taskId);
                   });
};

//Get the global concurrent dispatch queue and launch a few  tasks
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//TASK #1
//increment the task number and log
NSLog(@"Create Task Number %d", ++taskIdBlock);

//dispatch the task to the global queue. 
dispatch_async(globalConcurrentQueue, ^{
    runTask(taskIdBlock,5);
});

//TASK #2
//increment the task number and log
NSLog(@"Create Task Number %d", ++taskIdBlock);

//dispatch the task to the global queue.
dispatch_async(globalConcurrentQueue, ^{

    runTask(taskIdBlock,3);
});

出力:

Create Task Number 1
Create Task Number 2
Running Task: 1
Running Task: 2
Completed Task 2
Completed Task 1

コードセット #2 (スコープからのオブジェクト参照コピー)

//Integer Object
NSInteger *taskIdBlock = &(self->taskNumber);

//declare a block that takes in an ID and sleep time.
void (^runTask)(int taskId, int sleepTime);

//Create and assign the block
runTask = ^void(int taskId, int sleepTime)
{
    NSLog(@"Running Task: %d", taskId);
    // wait for x seconds before completing this method
    [NSThread sleepForTimeInterval:sleepTime];

    //update the main UI
    //tell the main thread we are finished with this task.
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       NSLog(@"Completed Task %d",taskId);
                   });
};

//Get the global concurrent dispatch queue and launch a few  tasks
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//TASK #1
//increment the task number and log
NSLog(@"Create Task Number %d", ++(*taskIdBlock));

//dispatch the task to the global queue. 
dispatch_async(globalConcurrentQueue, ^{
    runTask(*taskIdBlock,5);
});

//TASK #2
//increment the task number and log
NSLog(@"Create Task Number %d", ++(*taskIdBlock));

//dispatch the task to the global queue.
dispatch_async(globalConcurrentQueue, ^{

    runTask(*taskIdBlock,3);
});

出力:

Create Task Number 1
Running Task: 2
Create Task Number 2
Running Task: 2
Completed Task 2
Completed Task 2

各コードの 1 行目に注目してください。オブジェクト NSinteger へのプリミティブ int。私はこのようなものを見たいと思っていました:

dispatch_async(globalConcurrentQueue,runTask(*taskIdBlock,3));

ただし、これはコンパイルされません。将来的にはこれがより困難になるとしか思えないので、最初に確かな例を下に置くことをお勧めします. 前もって感謝します。

4

1 に答える 1

5

あなたが言った:

オブジェクティブ C ブロックで気になる点は次のとおりです。ブロックによってアクセスされる変数は、ブロックが後でアクセスできるように、ヒープ上のブロック データ構造にコピーされます。コピーされたポインター参照は、複数のスレッドが同じオブジェクトにアクセスしていることを意味する場合があります。

はい、ブロック内のポインターをキャプチャしてから、ポイント先のメモリにアクセス/変更すると、インターロックされていないアクセスが発生する可能性があります。典型的なアプローチは、不変のデータ構造を使用することです。たとえば、NSDataオブジェクトを作成できますが、それは変更できNSDataないNSMutableDataことがわかっているためです。NSDataあるブロックが別のブロックからデータの内容を変更することはできないため、複数のブロックでそれへのポインターをキャプチャすることは問題ありません。

同時に実行できるブロック間で可変状態を共有する必要がある場合は、他のマルチスレッド プログラミングと同様に、何らかの方法でその状態へのアクセスをインターロックする必要があります。それを行う慣用的な GCD の方法は、別のdispatch_queue_t. 以下に簡単な例を示します。

// This is a pointer to our shared state
NSInteger* sharedStatePtr = calloc(1, sizeof(*sharedStatePtr));

// This is a queue that we will use to protect our shared state
dispatch_queue_t sharedStateAccessQueue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);

// First work block
dispatch_block_t a = ^{
    __block NSInteger localValue = 0;

    // Safely read from shared state -- ensures no writers writing concurrently -- multiple readers allowed.
    dispatch_sync(sharedStateAccessQueue, ^{ localValue = *sharedStatePtr; });

    // do stuff
    localValue++;

    // Safely write to shared state -- ensures no readers reading concurrently.
    dispatch_barrier_async(sharedStateAccessQueue, { *sharedStatePtr = localValue; });
};

// Second work block
dispatch_block_t b = ^{
    __block NSInteger localValue = 0;

    // Safely read from shared state -- ensures no writers writing concurrently -- multiple readers allowed.
    dispatch_sync(sharedStateAccessQueue, ^{ localValue = *sharedStatePtr; });

    // do stuff
    localValue--;

    // Safely write to shared state -- ensures no readers reading concurrently.
    dispatch_barrier_async(sharedStateAccessQueue, { *sharedStatePtr = localValue; });
};

// Dispatch both blocks to a concurrent queue for execution.
dispatch_async(dispatch_get_global_queue(0, 0), a);
dispatch_async(dispatch_get_global_queue(0, 0), b);

aこれは、ブロックとの間の競合状態に対処するものではbありませんが、書き込みと読み取りの重複によって共有状態が破壊されないようにし、共有状態のすべてのアクセサー/ミューテーターのみを条件として、あらゆる種類の共有可変状態に対して機能します。dispatch_/dispatch_barrier_パターンを介して行います。

読み取り、何らかの作業を行ってからアトミックに書き込む必要がある場合は、次のようにシリアル キューを使用する方が簡単です。

// This is a pointer to our shared state
NSInteger* sharedStatePtr = calloc(1, sizeof(*sharedStatePtr));

// This is a queue that we will use to protect our shared state
dispatch_queue_t sharedStateAccessQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);

// First work block
dispatch_block_t a = ^{
    // Do some expensive work to determine what we want to add to the shared state
    NSInteger toAdd = SomeExpensiveFunctionWeWantToExecuteConcurrently();

    dispatch_async(sharedStateAccessQueue, ^{
        *sharedStatePtr = *sharedStatePtr + toAdd;
    });
};

// Second work block
dispatch_block_t b = ^{
    // Do some expensive work to determine what we want to subtract to the shared state
    NSInteger toSubtract = SomeOtherExpensiveFunctionWeWantToExecuteConcurrently();

    dispatch_async(sharedStateAccessQueue, ^{
        *sharedStatePtr = *sharedStatePtr - toSubtract;
    });
};

// Dispatch both blocks to a concurrent queue for execution.
dispatch_async(dispatch_get_global_queue(0, 0), a);
dispatch_async(dispatch_get_global_queue(0, 0), b);

GCD はいくつかの興味深いツールを提供しますが、それでも共有状態に注意する必要があります。共有状態を保護するためにキューを使用することは間違いなく慣用的な GCD の方法ですが、ロックのようなより古典的なメカニズム (それを行うと遅くなる可能性があります) や、共有状態を変更するプラットフォーム アトミックOSAtomicIncrement*を使用することもできます。OSAtomicCompareAndSwap*

いくつかの注意事項:NSIntegerはオブジェクトではありません。これは、API/コードをプラットフォーム/コンパイル ターゲットの違いから保護する便利な typedef です (つまり、NSInteger を使用すると、32 ビット プラットフォームでは 32 ビット int になり、64 ビット プラットフォームでは 64 ビット int になります)。

于 2013-09-12T13:44:22.000 に答える