将数据对象传递给Grand Central Dispatch Task的最佳方法

时间:2013-09-12 02:51:52

标签: objective-c multithreading grand-central-dispatch

问题:使用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));

但是这不能编译。我只能看到将来变得越来越困难,所以最好首先得到一个坚实的例子。提前谢谢。

1 个答案:

答案 0 :(得分: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);

这对解决块ab之间的竞争条件没有任何作用,但它确实可以确保共享状态不会因重叠写入和读取而被破坏,并且可以用于任何类型的共享可变状态,前提是该共享状态的所有访问器/变换器仅通过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*和{{ 1}}改变共享状态。

还有一些注释:OSAtomicCompareAndSwap*不是对象。它只是一个方便的typedef,可以保护API /代码免受平台/编译目标的差异(即如果使用NSInteger,它将在32位平台上为32位int,在64位平台上为64位int。)