问题:使用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));
但是这不能编译。我只能看到将来变得越来越困难,所以最好首先得到一个坚实的例子。提前谢谢。
答案 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);
这对解决块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*
和{{ 1}}改变共享状态。
还有一些注释:OSAtomicCompareAndSwap*
不是对象。它只是一个方便的typedef,可以保护API /代码免受平台/编译目标的差异(即如果使用NSInteger,它将在32位平台上为32位int,在64位平台上为64位int。)