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

注意每段代码的第一行。对象 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不是对象。它只是一种方便的 typedef,可以保护 API/代码免受平台/编译目标差异的影响(即,如果您使用 NSInteger,它将是 32 位平台上的 32 位 int 和 64 位平台上的 64 位 int。)

于 2013-09-12T13:44:22.000 回答