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

LEO*_*LEO 3 multithreading objective-c 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);
});
Run Code Online (Sandbox Code Playgroud)

输出:

Create Task Number 1
Create Task Number 2
Running Task: 1
Running Task: 2
Completed Task 2
Completed Task 1
Run Code Online (Sandbox Code Playgroud)

代码集#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);
});
Run Code Online (Sandbox Code Playgroud)

输出:

Create Task Number 1
Running Task: 2
Create Task Number 2
Running Task: 2
Completed Task 2
Completed Task 2
Run Code Online (Sandbox Code Playgroud)

注意每段代码中的第一行.对象NSinteger的原始int.我希望看到这样的事情:

dispatch_async(globalConcurrentQueue,runTask(*taskIdBlock,3));
Run Code Online (Sandbox Code Playgroud)

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

ipm*_*mcc 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);
Run Code Online (Sandbox Code Playgroud)

这无助于解决块之间的竞争条件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);
Run Code Online (Sandbox Code Playgroud)

虽然GCD为您提供了一些有趣的工具,但您仍然需要注意共享状态.虽然使用队列来保护共享的状态,可以说是地道的GCD的方式来做到这一点,你也可以你更经典的机制,例如锁(虽然它很可能会慢一些,以做到这一点)或平台原子能喜欢OSAtomicIncrement*OSAtomicCompareAndSwap*变异共享状态.

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