2013-09-12 3 views
3

Вопрос: Какова предпочтительная/лучшая/принятая практика передачи данных (помимо примитивов) в фоновое задание с использованием Grand Central Dispatch (GCD)?Лучший способ передать объекты данных в Grand Central Dispatch Task

Что касается меня с объективными блоками 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 (Object Reference Copy из области)

//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 

Обратите внимание на первую строку в каждом фрагменте кода. Примитив int для объекта NSinteger. Мне бы хотелось увидеть что-то вроде этого:

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

Однако это не скомпилировано. Я могу только видеть, что это становится более трудным в будущем, поэтому лучше всего получить твердый пример. Заранее спасибо.

ответ

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-битный int на 32-битных платформах и 64-битный int на 64-битных платформах.)

+0

Спасибо за ответ. Я быстро использовал NSInteger, думая, что это объект, а не typedef. То, что вы ставите, имеет смысл. Я должен начать видеть задачи как «внутренние потоки в Java». С нетерпением ждем, если кто-нибудь еще получит какие-либо рекомендации. – LEO

Смежные вопросы