Soyo

GCD queues : serial and concurrent

If you are reading this post, I wish you a merry christmas and new successful 2020.

It has been long time since the firstday I started working with iOS platform.
Until now, I feel myself still haven't mastered GCD yet.
So I decide to invest my time to understand the concept and how queues work.

Let take a look at the image below:

Screen-Shot-2019-12-25-at-12.00.55-PM

We could imagine queue is a list of tasks. We can create a custom task, append it to the queue and wait for tasks to be executed FIFO. A queue has 5 published properties:

  • label : String // custom name of a queue, eg: "com.ios.samplequeue1".
  • qos : DispatchQoS.QoSClass //quality of service level, can undestand like priority.
  • attributes : DispatchQueue.Attributes // define type of queue serial or concurrent, also use for marking queue not auto activate, default is serial.
  • autoreleaseFrequency : DispatchQueue.AutoreleaseFrequency //define how queue's autorelease pool created : inherit (from target queue), workItem (for blocks), never (not create any pool).
  • target : The target queue on which to execute blocks. Specify DISPATCH_TARGET_QUEUE_DEFAULT if you want the system to provide a queue that is appropriate for the current object.

We must specify them when initialize a queue. There're two system default queues: main queue and global queue. While main queue is serial, global queue is concurrent. We will talk about them in another post.

Okay, let see how the concurrent queue above works:

Screen-Shot-2019-12-25-at-12.03.26-PM

Here is the code, you can use playground to test

func doHeavyTask(_ number: Int, _ time: Double = 1000000) {
    print("==>S\(number): at \(Date())")
    usleep(useconds_t(time))
    print("-->E\(number): at \(Date())")
    print("===========")
}

let queue = DispatchQueue.init(label: "sample.queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: .none)

queue.async {
    doHeavyTask(1, 5 * 1e6)
}

queue.sync {
    doHeavyTask(2, 5 * 1e6)
}

queue.async {
    doHeavyTask(3, 5 * 1e6)
}

queue.sync {
    doHeavyTask(4, 7 * 1e6)
}

queue.sync {
    doHeavyTask(5, 1e7)
}

Console log:

==>S2: at 2019-12-24 05:48:46 +0000
==>S1: at 2019-12-24 05:48:46 +0000
-->E2: at 2019-12-24 05:48:51 +0000
===========
==>S4: at 2019-12-24 05:48:51 +0000
==>S3: at 2019-12-24 05:48:51 +0000
-->E1: at 2019-12-24 05:48:51 +0000
===========
-->E3: at 2019-12-24 05:48:56 +0000
===========
-->E4: at 2019-12-24 05:48:58 +0000
===========
==>S5: at 2019-12-24 05:48:58 +0000
-->E5: at 2019-12-24 05:49:08 +0000
===========

Despite task1 was called before task2, task2 was started and ended first. So the order here could not be predicted.

Unlike concurrent queue, all tasks in serial queue must wait for previous tasks completed. There are no different between async and sync tasks (?! why main queue is serial and we always do async tasks).

queue2-serial

There're only one task could be executed at the same time.
You could modify the above sources by changing attributes from .concurrent to .init() to confirm the serial queue.

Next post, I will talk about how task execute and how we prevent race conditions.

Happy coding.

Comments