Concurrent vs. serial determines how submitted tasks are to be run. A concurrent queue allows the tasks to run concurrently with one another. A serial queue only allows one of its tasks to run at a time.
Concurrent Queue
Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue. The currently executing tasks run on distinct threads that are managed by the dispatch queue. The exact number of tasks executing at any given point is variable and depends on system conditions.
To create a concurrent queue:
|
dispatch_queue_t queueA = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul); |
In iOS 5 and later, you can create concurrent dispatch queues yourself by specifying DISPATCH_QUEUE_CONCURRENT as the queue type. In addition, there are four predefined global concurrent queues for your application to use. For more information on how to get the global concurrent queues
Tasks are executed in Parallel
“Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue.”
Here we have an example of running Concurrent with async dispatching
A concurrent queue means that they are executed in parallel. Hence while block A may be processing, blocks B, C..etc may be executed at the same time as well. In other words, the current executing block can’t assume that it’s the only block running on that queue.
Also, because it’s a concurrent queue, it lets the async dispatching execute blocks whenever they are ready to. Hence that’s why the queue may dispatch its blocks out of sequence.
dispatch_async means control return immediately. In other words, “DON’T wait for me to finish my task, just go on with the next task….”. It DOES NOT BLOCK, which means the main thread (UI thread) keeps running and is responsive to user touches. This includes all other threads also, they keep going about their work because we are not blocking.
dispatch_sync means it blocks until it finishes processing. In other words, “WAIT for me to finish my task, then you can take over”. This BLOCKS all threads, including the main thread. So when you use dispatch_sync, all queues and threads wait for this to finish, including the UI thread so that it does not respond to user touches.
Concurrent Async example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
//block declaration typedef void (^OnBlockComplete) (BOOL complete, NSString * blockId, NSString * action); //dispatch concurrently in queue, a thread with an id, READ or WRITE, how much time -(void)dispatchConcurrentlyInQueue:(dispatch_queue_t)queue WithId:(NSString*)blockId AndAction:(NSString*)action AndTime:(int)timeUnit onSuccess:(OnBlockComplete)completeBlock { dispatch_async(queue, ^{ NSLog(@"^^^^^^^^^^^^^^ TASK %@ started ^^^^^^^^^^^^^^^", blockId); for ( int i = 0; i < timeUnit; i++) { //NSLog(@"Task %@ %u",blockId, i); NSLog(@"Task %@ %@", blockId, action); } completeBlock(TRUE, blockId, action); }); } dispatch_queue_t queue_concurrent = dispatch_queue_create("CONCURRENT A QUEUE", DISPATCH_QUEUE_CONCURRENT); [self dispatchConcurrentlyInQueue:queue_concurrent WithId:@"A" AndAction: @"READ" AndTime: 30 onSuccess:^(BOOL complete, NSString *blockId, NSString * action) { if(complete) { NSLog(@"----------> block %@ is done with %@ <-------------- ", blockId, action); } }]; [self dispatchConcurrentlyInQueue:queue_concurrent WithId:@"B" AndAction: @"WRITE" AndTime: 70 onSuccess:^(BOOL complete, NSString *blockId, NSString * action) { if(complete) { NSLog(@"----------> block %@ is done with %@ <-------------- ", blockId, action); } }]; [self dispatchConcurrentlyInQueue:queue_concurrent WithId:@"C" AndAction: @"READ" AndTime: 30 onSuccess:^(BOOL complete, NSString *blockId, NSString * action) { if(complete) { NSLog(@"----------> block %@ is done with %@ <-------------- ", blockId, action); } }]; |
In the code, we’re simply simulating spawning threads concurrently to do certain tasks in certain amount of time units.
^^^^^^^^^^^^^^ TASK C started ^^^^^^^^^^^^^^^
2015-08-01 00:36:28.321 YonoApp[4189:180141] ^^^^^^^^^^^^^^ TASK A started ^^^^^^^^^^^^^^^
2015-08-01 00:36:28.321 YonoApp[4189:180144] ^^^^^^^^^^^^^^ TASK B started ^^^^^^^^^^^^^^^
2015-08-01 00:36:28.321 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.321 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.321 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.321 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.321 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.321 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.322 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.323 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.323 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.323 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
CalendarViewController.m -initWithTabBar 1
2015-08-01 00:36:28.323 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.323 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.324 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.328 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.328 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.328 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.328 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.328 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.328 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.329 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.329 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.329 YonoApp[4189:180142] Task C UPDATE BBT TENDERNESS
2015-08-01 00:36:28.329 YonoApp[4189:180144] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:36:28.330 YonoApp[4189:180142] ———-> Task C is done with UPDATE BBT TENDERNESS <--------------
2015-08-01 00:36:28.330 YonoApp[4189:180141] Task A UPDATE SIGN TENDERNESS
....A gets done
..then B is done
So as you can see, even though we ran it concurrently via dispatch_async, Task C started first because task A and task B were not ready. After C started, then A, and then B.
While A is working…B is working….etc ….and they keep mingling. This happens between all the threads
In other words, your code will not wait for execution to complete. Both blocks will dispatch (and be enqueued) to the queue and the rest of your code will continue executing on that thread. Then at some point in the future, (depending on what else has been dispatched to your queue), Task A will execute and then Task B will execute.
Concurrent Sync example
In the dispatch_sync example, however, you won’t dispatch TASK n+1 until after TASK n has been dispatched and executed. This is called “blocking”. Your code waits (or “blocks”) until the task executes. If were to change dispatch_async to dispatch_sync, then the result would be like this:
2015-08-01 00:40:09.076 YonoApp[4231:181577] ^^^^^^^^^^^^^^ TASK A started ^^^^^^^^^^^^^^^
2015-08-01 00:40:09.076 YonoApp[4231:181577] Task A UPDATE SIGN TENDERNESS
….
2015-08-01 00:40:09.084 YonoApp[4231:181577] Task A UPDATE SIGN TENDERNESS
2015-08-01 00:40:09.084 YonoApp[4231:181577] ———-> Task A is done
2015-08-01 00:40:09.084 YonoApp[4231:181577] ^^^^^^^^^^^^^^ TASK B started ^^^^^^^^^^^^^^^
2015-08-01 00:40:09.084 YonoApp[4231:181577] Task B UPDATE SIGN TENDERNESS
………
2015-08-01 00:40:09.090 YonoApp[4231:181577] Task B UPDATE SIGN TENDERNESS
2015-08-01 00:40:09.091 YonoApp[4231:181577] ———-> Task B is done
2015-08-01 00:40:09.091 YonoApp[4231:181577] ^^^^^^^^^^^^^^ TASK C started ^^^^^^^^^^^^^^^
2015-08-01 00:40:09.091 YonoApp[4231:181577] Task C UPDATE BBT TENDERNESS
…………
2015-08-01 00:40:09.092 YonoApp[4231:181577] Task C UPDATE BBT TENDERNESS
2015-08-01 00:40:09.092 YonoApp[4231:181577] ———-> Task C is done
Serial Queues
Serial queues are monogamous, but uncommitted. If you give a bunch of tasks to each serial queue, it will run them one at a time, using only one thread at a time. The uncommitted aspect is that serial queues may switch to a different thread between tasks.
Serial queues always wait for a task to finish before going to the next one.
Thus tasks are completed in FIFO order. You can make as many serial queues as you need with dispatch_queue_create.
By definition, Serial Queues says that there is only one block running at a time, and they are executed in order.
So if we add in blocks A, B, C, D…then they are started and ended in order. Also notice since we use dispatch_async, that means it returns control to the Main Thread, for other threads to start spawning.
To create a serial queue:
|
dispatch_queue_t queueS = dispatch_queue_create("Serial Queue", NULL); |
If we dispatched async for tasks A, B, and C
|
dispatch_async(queue, ^{ NSLog(@"^^^^^^^^^^^^^^ TASK %@ started ^^^^^^^^^^^^^^^", blockId); for ( int i = 0; i < timeUnit; i++) { ////NSLog(@"Task %@ %u",blockId, i); NSLog(@"Task %@ %@", blockId, action); } completeBlock(TRUE, blockId, action); }); |
The result would be:
Task A started
Task A ended
Task B started
Task B ended
Task C started
Task C ended
Because by definition serial only allows on task to be running at one time. Async means it does not block, so it is not blocking anything while we run. Hence another serial queue may be running its task at the same time.
If we dispatched sync for tasks A, B, and C, we’d get the same result because by definition sync blocks everyone while it works. Once its done, it unblocks and let’s the next task go.
Multiple Serial Queues
However, if you create four serial queues, each queue executes only one task at a time up to four tasks could still execute concurrently, one from each queue.
Let’s see what happens when we get 2 serial queues together.
2 Serial queues, using Async
serialQueue1 – Task A
serialQueue2 – Task B C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
[self dispatchInQueue:serialQueue1 WithId:@"A1" AndAction: @"UPDATE SIGN TENDERNESS" AndTime: 30 onSuccess:^(BOOL complete, NSString *blockId, NSString * action) { if(complete) { NSLog(@"----------> Task %@ is done with %@ <-------------- ", blockId, action); } }]; [self dispatchInQueue:serialQueue2 WithId:@"B2" AndAction: @"UPDATE SIGN TENDERNESS" AndTime: 40 onSuccess:^(BOOL complete, NSString *blockId, NSString * action) { if(complete) { NSLog(@"----------> Task %@ is done with %@ <-------------- ", blockId, action); } }]; [self dispatchInQueue:serialQueue2 WithId:@"C2" AndAction: @"UPDATE BBT TENDERNESS" AndTime: 10 onSuccess:^(BOOL complete, NSString *blockId, NSString * action) { if(complete) { NSLog(@"----------> Task %@ is done with %@ <-------------- ", blockId, action); } }]; |
2015-08-01 01:23:47.782 YonoApp[4451:196545] ^^^^^^^^^^^^^^ TASK A1 started ^^^^^^^^^^^^^^^
2015-08-01 01:23:47.782 YonoApp[4451:196544] ^^^^^^^^^^^^^^ TASK B2 started ^^^^^^^^^^^^^^^
2015-08-01 01:23:47.786 YonoApp[4451:196545] Task A1 UPDATE SIGN TENDERNESS
2015-08-01 01:23:47.786 YonoApp[4451:196544] Task B2 UPDATE SIGN TENDERNESS
RIGHT HERE. task A1 and task B2 are executing at the same time. In respective to their own queues, they are running one task at a time, but from a multiple queue standpoint, they are running their one task at a time simultaneously with each other.
2015-08-01 01:23:47.794 YonoApp[4451:196544] Task B2 UPDATE SIGN TENDERNESS
2015-08-01 01:23:47.795 YonoApp[4451:196544] Task B2 UPDATE SIGN TENDERNESS
2015-08-01 01:23:47.795 YonoApp[4451:196544] ———-> Task B2 is done with UPDATE SIGN TENDERNESS <--------------
2015-08-01 01:23:47.795 YonoApp[4451:196544] ^^^^^^^^^^^^^^ TASK C2 started ^^^^^^^^^^^^^^^
2015-08-01 01:23:47.797 YonoApp[4451:196544] Task C2 UPDATE BBT TENDERNESS
2015-08-01 01:23:47.797 YonoApp[4451:196544] Task C2 UPDATE BBT TENDERNESS
2015-08-01 01:23:47.797 YonoApp[4451:196544] ----------> Task C2 is done with UPDATE BBT TENDERNESS <--------------
Hence, having multiple serial queues simply means each queue's block work individually and in order, but the serial queues themselves are parallel.
Multiple Serial Queues with Sync
When we have multiple serial queues, and we want them to be in order, we can use dispatch_sync
|
dispatch_sync(queue, ^{ NSLog(@"^^^^^^^^^^^^^^ TASK %@ started ^^^^^^^^^^^^^^^", blockId); for ( int i = 0; i < timeUnit; i++) { //NSLog(@"Task %@ %u",blockId, i); NSLog(@"Task %@ %@", blockId, action); } completeBlock(TRUE, blockId, action); }); |
dispatch_sync means that while a block is executing in this particular queues, ALL OTHER BLOCKS are on hold…until this one finishes. Hence before when we had multiple serial queues doing their reads and writes, you see read/write overlaps because even though one serial queue is doing it one block at a time, the other serial queue(s) are doing their one block at a time as well…resulting in “the one and only working blocks” from multiple serial queues doing their work at the same time.
In order to solve this, we use dispatch_sync, which means for all other blocks to hold and let this block finish. When this block is finished, then we let the next block start.
We you apply dispatch_sync to the code, you’ll see that all tasks are done in order and without overlapping:
^^^^^^^^^^^^^^ TASK A1 started ^^^^^^^^^^^^^^^
2015-08-01 01:27:26.061 YonoApp[4489:197886] Task A1 UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.136 YonoApp[4489:197886] Task A1 UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.136 YonoApp[4489:197886] ———-> Task A1 is done with UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.137 YonoApp[4489:197886] ^^^^^^^^^^^^^^ TASK B2 started ^^^^^^^^^^^^^^^
2015-08-01 01:27:26.137 YonoApp[4489:197886] Task B2 UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.217 YonoApp[4489:197886] Task B2 UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.218 YonoApp[4489:197886] Task B2 UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.218 YonoApp[4489:197886] ———-> Task B2 is done with UPDATE SIGN TENDERNESS
2015-08-01 01:27:26.218 YonoApp[4489:197886] ^^^^^^^^^^^^^^ TASK C2 started ^^^^^^^^^^^^^^^
2015-08-01 01:27:26.218 YonoApp[4489:197886] Task C2 UPDATE BBT TENDERNESS
2015-08-01 01:27:26.219 YonoApp[4489:197886] Task C2 UPDATE BBT TENDERNESS
2015-08-01 01:27:26.221 YonoApp[4489:197886] Task C2 UPDATE BBT TENDERNESS
2015-08-01 01:27:26.221 YonoApp[4489:197886] ———-> Task C2 is done with UPDATE BBT TENDERNESS
Other Notes
Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue. The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue. Serial queues are often used to synchronize access to a specific resource.
You can create as many serial queues as you need, and each queue operates concurrently with respect to all other queues. In other words, if you create four serial queues, each queue executes only one task at a time but up to four tasks could still execute concurrently, one from each queue.
Serial means the tasks are executed in order. This means that the block of the queue that is executing can assume IT IS THE ONLY BLOCK RUNNING ON THAT QUEUE. However, blocks from other queues may be running concurrently with this queue. That’s why you need to use dispatch_sync to make sure ONLY ONE BLOCK is running from a multiple queue standpoint.
Concurrent means the tasks are executed in parallel. This means that the block of the queue that is executing CAN’T assume that its the only block running on that queue.