Concurrent vs serial queues in GCD

61,438

Solution 1

A simple example: you have a block that takes a minute to execute. You add it to a queue from the main thread. Let's look at the four cases.

  • async - concurrent: the code runs on a background thread. Control returns immediately to the main thread (and UI). The block can't assume that it's the only block running on that queue
  • async - serial: the code runs on a background thread. Control returns immediately to the main thread. The block can assume that it's the only block running on that queue
  • sync - concurrent: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can't assume that it's the only block running on that queue (I could have added another block using async a few seconds previously)
  • sync - serial: the code runs on a background thread but the main thread waits for it to finish, blocking any updates to the UI. The block can assume that it's the only block running on that queue

Obviously you wouldn't use either of the last two for long running processes. You normally see it when you're trying to update the UI (always on the main thread) from something that may be running on another thread.

Solution 2

Here are a couple of experiments that i have done to make me understand about these serial, concurrent queues with Grand Central Dispatch.

 func doLongAsyncTaskInSerialQueue() {

   let serialQueue = DispatchQueue(label: "com.queue.Serial")
      for i in 1...5 {
        serialQueue.async {

            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

Task will run in different thread(other than main thread) when you use async in GCD. Async means execute next line do not wait until the block executes which results non blocking main thread & main queue. Since its serial queue, all are executed in the order they are added to serial queue.Tasks executed serially are always executed one at a time by the single thread associated with the Queue.

func doLongSyncTaskInSerialQueue() {
    let serialQueue = DispatchQueue(label: "com.queue.Serial")
    for i in 1...5 {
        serialQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

Task may run in main thread when you use sync in GCD. Sync runs a block on a given queue and waits for it to complete which results in blocking main thread or main queue.Since the main queue needs to wait until the dispatched block completes, main thread will be available to process blocks from queues other than the main queue.Therefore there is a chance of the code executing on the background queue may actually be executing on the main thread Since its serial queue, all are executed in the order they are added(FIFO).

func doLongASyncTaskInConcurrentQueue() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.async {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executing")
    }
}

Task will run in background thread when you use async in GCD. Async means execute next line do not wait until the block executes which results non blocking main thread. Remember in concurrent queue, task are processed in the order they are added to queue but with different threads attached to the queue. Remember they are not supposed to finish the task as the order they are added to the queue.Order of task differs each time threads are created as necessarily automatically.Task are executed in parallel. With more than that(maxConcurrentOperationCount) is reached, some tasks will behave as a serial until a thread is free.

func doLongSyncTaskInConcurrentQueue() {
  let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executed")
    }
}

Task may run in main thread when you use sync in GCD. Sync runs a block on a given queue and waits for it to complete which results in blocking main thread or main queue.Since the main queue needs to wait until the dispatched block completes, main thread will be available to process blocks from queues other than the main queue.Therefore there is a chance of the code executing on the background queue may actually be executing on the main thread. Since its concurrent queue, tasks may not finish in the order they are added to queue. But with synchronous operation it does although they may be processed by different threads. So, it behaves as this is the serial queue.

Here is a summary of these experiments

Remember using GCD you are only adding task to the Queue and performing task from that queue. Queue dispatches your task either in main or background thread depending on whether operation is synchronous or asynchronous. Types of queues are Serial,Concurrent,Main dispatch queue.All the task you perform is done by default from Main dispatch queue.There are already four predefined global concurrent queues for your application to use and one main queue(DispatchQueue.main).You can also manually create your own queue and perform task from that queue.

UI Related task should always be performed from main thread by dispatching the task to Main queue.Short hand utility is DispatchQueue.main.sync/async whereas network related/heavy operations should always be done asynchronously no matters which ever thread you are using either main or background

EDIT: However, There are cases you need to perform network calls operations synchronously in a background thread without freezing UI(e.g.refreshing OAuth Token and wait if it succeed or not).You need to wrap that method inside a asynchronous operation.This way your heavy operations are executed in the order and without Blocking main thread.

func doMultipleSyncTaskWithinAsynchronousOperation() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    concurrentQueue.async {
        let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        for i in 1...5 {
            concurrentQueue.sync {
                let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imgURL)
                print("\(i) completed downloading")
            }
            print("\(i) executed")
        }
    }
}

EDIT EDIT: You can watch demo video here

Solution 3

First, it's important to know the difference between threads and queues and what GCD really does. When we use dispatch queues (through GCD), we're really queueing, not threading. The Dispatch framework was designed specifically to get us away from threading, as Apple admits that "implementing a correct threading solution [can] become extremely difficult, if not [sometimes] impossible to achieve." Therefore, to perform tasks concurrently (tasks that we don't want freezing the UI), all we need to do is create a queue of those tasks and hand it to GCD. And GCD handles all of the associated threading. Therefore, all we're really doing is queueing.

The second thing to know right away is what a task is. A task is all of the code within that queue block (not within the queue, because we can add things to a queue all of the time, but within the closure where we added it to the queue). A task is sometimes referred to as a block and a block is sometimes referred to as a task (but they are more commonly known as tasks, particularly in the Swift community). And no matter how much or little code, all of the code within the curly braces are considered a single task:

serialQueue.async {
    // this is one task
    // it can be any number of lines with any number of methods
}
serialQueue.async {
    // this is another task added to the same queue
    // this queue now has two tasks
}

And it's obvious mentioning that concurrent simply means at the same time with other things and serial means one after the other (never at the same time). To serialize something, or to put something in serial, just means to execute it from start to finish in its order from left to right, top to bottom, uninterrupted.

There are two types of queues, serial and concurrent, but all queues are concurrent relative to each other. The fact that you want to run any code "in the background" means that you want to run it concurrently with another thread (usually the main thread). Therefore, all dispatch queues, serial or concurrent, execute their tasks concurrently relative to other queues. Any serialization performed by queues (by serial queues), have only to do with the tasks within that single [serial] dispatch queue (like in the example above where there are two tasks within the same serial queue; those tasks will be executed one after the other, never simultaneously).

SERIAL QUEUES (often known as private dispatch queues) guarantee the execution of tasks one at a time from start to finish in the order that they were added to that specific queue. This is the only guarantee of serialization anywhere in the discussion of dispatch queues--that the specific tasks within a specific serial queue are executed in serial. Serial queues can, however, run simultaneously with other serial queues if they are separate queues because, again, all queues are concurrent relative to each other. All tasks run on distinct threads but not every task is guaranteed to run on the same thread (not important, but interesting to know). And the iOS framework does not come with any ready-to-use serial queues, you must make them. Private (non-global) queues are serial by default, so to create a serial queue:

let serialQueue = DispatchQueue(label: "serial")

You can make it concurrent through its attribute property:

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

But at this point, if you aren't adding any other attributes to the private queue, Apple recommends that you just use one of their ready-to-go global queues (which are all concurrent). At the bottom of this answer, you'll see another way to create serial queues (using the target property), which is how Apple recommends doing it (for more efficient resource management). But for now, labeling it is sufficient.

CONCURRENT QUEUES (often known as global dispatch queues) can execute tasks simultaneously; the tasks are, however, guaranteed to initiate in the order that they were added to that specific queue, but unlike serial queues, the queue does not wait for the first task to finish before starting the second task. Tasks (as with serial queues) run on distinct threads and (as with serial queues) not every task is guaranteed to run on the same thread (not important, but interesting to know). And the iOS framework comes with four ready-to-use concurrent queues. You can create a concurrent queue using the above example or by using one of Apple's global queues (which is usually recommended):

let concurrentQueue = DispatchQueue.global(qos: .default)

RETAIN-CYCLE RESISTANT: Dispatch queues are reference-counted objects but you do not need to retain and release global queues because they are global, and thus retain and release is ignored. You can access global queues directly without having to assign them to a property.

There are two ways to dispatch queues: synchronously and asynchronously.

SYNC DISPATCHING means that the thread where the queue was dispatched (the calling thread) pauses after dispatching the queue and waits for the task in that queue block to finish executing before resuming. To dispatch synchronously:

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

ASYNC DISPATCHING means that the calling thread continues to run after dispatching the queue and does not wait for the task in that queue block to finish executing. To dispatch asynchronously:

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

Now one might think that in order to execute a task in serial, a serial queue should be used, and that's not exactly right. In order to execute multiple tasks in serial, a serial queue should be used, but all tasks (isolated by themselves) are executed in serial. Consider this example:

whichQueueShouldIUse.syncOrAsync {
    for i in 1...10 {
        print(i)
    }
    for i in 1...10 {
        print(i + 100)
    }
    for i in 1...10 {
        print(i + 1000)
    }
}

No matter how you configure (serial or concurrent) or dispatch (sync or async) this queue, this task will always be executed in serial. The third loop will never run before the second loop and the second loop will never run before the first loop. This is true in any queue using any dispatch. It's when you introduce multiple tasks and/or queues where serial and concurrency really come into play.

Consider these two queues, one serial and one concurrent:

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

Say we dispatch two concurrent queues in async:

concurrentQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
103
3
104
4
105
5

Their output is jumbled (as expected) but notice that each queue executed its own task in serial. This is the most basic example of concurrency--two tasks running at the same time in the background in the same queue. Now let's make the first one serial:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

101
1
2
102
3
103
4
104
5
105

Isn't the first queue supposed to be executed in serial? It was (and so was the second). Whatever else happened in the background is not of any concern to the queue. We told the serial queue to execute in serial and it did... but we only gave it one task. Now let's give it two tasks:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

And this is the most basic (and only possible) example of serialization--two tasks running in serial (one after the other) in the background (to the main thread) in the same queue. But if we made them two separate serial queues (because in the above example they are the same queue), their output is jumbled again:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue2.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
3
103
4
104
5
105

And this is what I meant when I said all queues are concurrent relative to each other. These are two serial queues executing their tasks at the same time (because they are separate queues). A queue does not know or care about other queues. Now lets go back to two serial queues (of the same queue) and add a third queue, a concurrent one:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 1000)
    }
}

1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005

That's kind of unexpected, why did the concurrent queue wait for the serial queues to finish before it executed? That's not concurrency. Your playground may show a different output but mine showed this. And it showed this because my concurrent queue's priority wasn't high enough for GCD to execute its task sooner. So if I keep everything the same but change the global queue's QoS (its quality of service, which is simply the queue's priority level) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), then the output is as expected:

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

The two serial queues executed their tasks in serial (as expected) and the concurrent queue executed its task quicker because it was given a high priority level (a high QoS, or quality of service).

Two concurrent queues, like in our first print example, show a jumbled printout (as expected). To get them to print neatly in serial, we would have to make both of them the same serial queue (the same instance of that queue, as well, not just the same label). Then each task is executed in serial with respect to the other. Another way, however, to get them to print in serial is to keep them both concurrent but change their dispatch method:

concurrentQueue.sync {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

Remember, sync dispatching only means that the calling thread waits until the task in the queue is completed before proceeding. The caveat here, obviously, is that the calling thread is frozen until the first task completes, which may or may not be how you want the UI to perform.

And it is for this reason that we cannot do the following:

DispatchQueue.main.sync { ... }

This is the only possible combination of queues and dispatching methods that we cannot perform—synchronous dispatching on the main queue. And that's because we are asking the main queue to freeze until we execute the task within the curly braces... which we dispatched to the main queue, which we just froze. This is called deadlock. To see it in action in a playground:

DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
    print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock

One last thing to mention is resources. When we give a queue a task, GCD finds an available queue from its internally-managed pool. As far as the writing of this answer, there are 64 queues available per qos. That may seem like a lot but they can quickly be consumed, especially by third-party libraries, particularly database frameworks. For this reason, Apple has recommendations about queue management (mentioned in the links below); one being:

Instead of creating private concurrent queues, submit tasks to one of the global concurrent dispatch queues. For serial tasks, set the target of your serial queue to one of the global concurrent queues. That way, you can maintain the serialized behavior of the queue while minimizing the number of separate queues creating threads.

To do this, instead of creating them like we did before (which you still can), Apple recommends creating serial queues like this:

let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))

And using an extension, we can get it down to this:

extension DispatchQueue {
    public class func serial(label: String, qos: DispatchQoS = .default) -> DispatchQueue {
        return DispatchQueue(label: label,
                             qos: qos,
                             attributes: [],
                             autoreleaseFrequency: .inherit,
                             target: .global(qos: qos.qosClass))
    }
}

let defaultSerialQueue = DispatchQueue.serial(label: "xyz")
let serialQueue = DispatchQueue.serial(label: "xyz", qos: .userInteractive)

// Which now looks like the global initializer
let concurrentQueue = DispatchQueue.global(qos: .default)

For further reading, I recommend the following:

https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1

https://developer.apple.com/documentation/dispatch/dispatchqueue

Solution 4

I like to think this using this metaphor (Here's the link to the original image):

Dad's gonna need some help

Let's imagine your dad is doing the dishes and you've just had a glass of soda. You bring the glass to your dad to clean it up, putting it besides the other dish.

Now your dad is doing the dishes all by himself, so he's going to have to do them one by one: Your dad here represents a serial queue.

But you're not really interested in standing there and watching it get cleaned up. So, you drop the glass, and go back to your room: this is called an async dispatch. Your dad might or might not let you know once he's done but the important bit is that you're not waiting for the glass to be cleaned up; you go back to your room to do, you know, kid stuff.

Now let's assume you're still thirsty and want to have some water on that same glass that happens to be your favourite, and you really want it back as soon as it's cleaned up. So, you stand there and watch your dad doing the dishes until yours is done. This is a sync dispatch, since you're blocked while you are waiting for the task to be finished.

And finally let's say your mom decides to help your dad and joins him doing the dishes. Now the queue becomes a concurrent queue since they can clean multiple dishes at the same time; but note that you can still decide to wait there or go back to your room, regardless of how they work.

Hope this helps

Solution 5

If I understand correctly about how GCD works, I think there are two types of DispatchQueue, serial and concurrent, at the same time, there are two way how DispatchQueue dispatch its tasks, the assigned closure, first one is async, and the other is sync. Those together determines how the closure (task) actually is executed.

I found that serial and concurrent mean how many threads that queue can use, serial means one, whereas concurrent means many. And sync and async mean the task will be executed on which thread, the caller's thread or the thread underlying that queue, sync means run on caller's thread whereas async means run on the underlying thread.

The following is experimental code that can run on Xcode playground.

PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")

func codeFragment() {
  print("code Fragment begin")
  print("Task Thread:\(Thread.current.description)")
  let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
  let _ = try! Data(contentsOf: imgURL)
  print("code Fragment completed")
}

func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }

func tasksExecution() {
  (1...5).forEach { (_) in
    /// Using an concurrent queue to simulate concurent task executions.
    cq.async {
      print("Caller Thread:\(Thread.current.description)")
      /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
      //serialQueueAsync()
      /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
      //serialQueueSync()
      /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
      //concurrentQueueAsync()
      /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
      //concurrentQueueSync()
    }
  }
}
tasksExecution()

Hope it can be helpful.

Share:
61,438
Bogdan Alexandru
Author by

Bogdan Alexandru

Embedded Software Engineer

Updated on July 08, 2022

Comments

  • Bogdan Alexandru
    Bogdan Alexandru almost 2 years

    I'm struggling to fully understand the concurrent and serial queues in GCD. I have some issues and hoping someone can answer me clearly and at the point.

    1. I'm reading that serial queues are created and used in order to execute tasks one after the other. However, what happens if:

      • I create a serial queue
      • I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C

      Will the three blocks be executed:

      • in order A,B,C because the queue is serial

        OR

      • concurrently (in the same time on parralel threads) because I used ASYNC dispatch
    2. I'm reading that I can use dispatch_sync on concurrent queues in order to execute blocks one after the other. In that case, WHY do serial queues even exist, since I can always use a concurrent queue where I can dispatch SYNCHRONOUSLY as many blocks as I want?

      Thanks for any good explanation!

  • Bogdan Alexandru
    Bogdan Alexandru over 10 years
    So you are telling me that: (1)the queue's type (conc or serial) is the ONLY element that decides whether the tasks are executed in order or in paralel;; (2)the dispatch type (sync or async) is only saying whether the execution goes OR doesn't go to the next instruction? I mean, if I dispatch a task SYNC the code will block until that tasks finishes, no matter what queue it's executed on?
  • Jano
    Jano over 10 years
    @BogdanAlexandru Correct. The queue dictates the execution policy, not how you queue the block. Sync waits for the block to complete, async doesn't.
  • rb612
    rb612 almost 9 years
    when you said, "Obviously you would used either of the last two for long running processes.", you meant that you wouldn't use dispatch_sync for the long running processes right?
  • Stephen Darlington
    Stephen Darlington over 8 years
    @rb612 Very delayed response, but yes. Good catch.
  • Admin
    Admin about 8 years
    @StephenDarlington does that mean when i use concurrent queues...all the task added to the queue are executed parallely in background thread?
  • Stephen Darlington
    Stephen Darlington about 8 years
    @swiftBUTCHER Up to a certain point, yes. When you create a queue you can specify the maximum number of threads. If you add fewer tasks than that they will execute in parallel. With more than that, some tasks will remain in a queue until there's available capacity.
  • Admin
    Admin about 8 years
    @StephenDarlington thanks...that makes a whole lot sense to me.. is it the maxConcurrentOperationCount to execute the no. of task in a queue in parallel?
  • Pablo A.
    Pablo A. almost 8 years
    What if you use async on main thread dispatch_get_main_queue()? In all four cases you said the block runs on background, but I guess in this case it would run on main thread and it wouldn't return immediately to main thread because it's already in main thread. This scenario it's a bit unclear to me when it's not within a background block.
  • Stephen Darlington
    Stephen Darlington almost 8 years
    @PabloA., the main thread is a serial queue so there are only really two cases. Beyond that, it's exactly the same. Async returns immediately (and the block probably gets executed at the end of the current run loop). The main gotcha is if you do sync from the main thread to the main thread, in which case you get a deadlock.
  • mfaani
    mfaani over 7 years
    I see you writing as in if async & sync are something which dictate the relation of that queue with the main queue ie dispatch_get_main_queue did I get that right? But basically you could swap that with any current queue you are on correct?
  • Stephen Darlington
    Stephen Darlington over 7 years
    @Honey Correct.
  • mfaani
    mfaani over 7 years
    not exactly what the OP was asking but normally when from a background thread, like in a completion handler of a urlsession we do dipsatch_async(dispatch_get_main_queue) which is similar to the 2nd case as in : async - serial: the code runs on the main thread. Control returns immediately to the background thread ( yet we usually do this when we have a result and there's nothing else left to do). The block can assume that it's the only block running on that queue ( ie it's the only queue/entity that is messing around with the UI of the app—from the main queue). right?
  • mfaani
    mfaani over 7 years
    Great demonstration.... next line do not wait until the block executes which results non blocking main thread this is why if you use breakpoints on a background thread it will jump to the } because it really isn't executing at that moment
  • mfaani
    mfaani over 7 years
    also can I ask you to add examples of where you would do each of the 4 scenarios?
  • Sentry.co
    Sentry.co about 7 years
    @That lazy iOS Guy 웃 I still don't understand the difference between async concurrent and async serial. What is the implication of using either. They both run in the background not disturbing the UI. And why would you ever use sync? Isn't all code sync. one after the other?
  • Anish Parajuli 웃
    Anish Parajuli 웃 about 7 years
    @GitSyncApp you can watch the video here
  • Sentry.co
    Sentry.co about 7 years
    @ That lazy iOS Guy 웃: thx for making that. I posted on slack swift-lang. Would be 👌 If you could make one about DispatchGroup and DispatchWorkItem as well. :D
  • gabbler
    gabbler about 7 years
    I have tested your last one, the concurrentQueue.sync of doLongSyncTaskInConcurrentQueue() function, it prints main thread, Task will run in different thread seems not true.
  • Anish Parajuli 웃
    Anish Parajuli 웃 about 7 years
    @gabbler Yeah i agree but you can see this
  • gabbler
    gabbler about 7 years
    @ThatlazyiOSGuy웃, thanks, I am still confused though as my test results differ from yours.
  • mfaani
    mfaani about 7 years
    specifically about the main thread, didn't you say it's a serial queue itself? Meaning it won't matter if we sync or async on main thread? so just as you said it'll be 2 cases. In that sense your example is somewhat confusing, since it's a special case
  • Stephen Darlington
    Stephen Darlington about 7 years
    If you add to the main thread from the main thread and do so with sync, you'll get a deadlock.
  • Stephen Darlington
    Stephen Darlington about 7 years
    The main thread is only a special case in the sense that the OS uses it for UI operations. It can be treated as a serial queue otherwise.
  • Anish Parajuli 웃
    Anish Parajuli 웃 about 7 years
    @gabbler i have updated the answer.Let me know if it still confuses you
  • koira
    koira about 7 years
    There is a contradiction in your comments for concurrent queues. Under func doLongASyncTaskInConcurrentQueue() you are saying Since its concurrent queue, all are executed in the order they are added to queue... but under func doLongSyncTaskInConcurrentQueue(), you say Since its concurrent queue, tasks are not executed in the order they are added to queue.
  • Anish Parajuli 웃
    Anish Parajuli 웃 about 7 years
    @koira Thanks for pointing the typo mistake in sentence structure..I have fixed it
  • Saheb Roy
    Saheb Roy almost 7 years
    I am currently adding multiple tasks in order x1, x2 in a sync concurrent task, But they are finishing exactly in the order it is being added. If i add tasks in the queue then as it is a serial queue, it is waiting to return to the next execution of the block. Why is this?
  • Anish Parajuli 웃
    Anish Parajuli 웃 almost 7 years
    Yes, sync runs a block on a given queue and waits for it to complete which results in blocking, no matter whether it is serial or concurrent..You need to know about two different topics here..Synchronous/Asynchronous task and Serial/Concurrent Queue.Run all of the examples in the playground more than once and you can grasp the concept.
  • abhiDagwar
    abhiDagwar over 5 years
    What is the use of sync if its blocking your queue(code). Why we need to use sync?
  • Stephen Darlington
    Stephen Darlington over 5 years
    @user3812547 Use whatever makes sense in your code! I would default to async unless you have a good reason to do otherwise. An obvious reason to block would be the code returns a value, or you need to run a bunch of blocks on different threads in order. You can do that using async but it would be a lot harder.
  • Muhammad Shauket
    Muhammad Shauket over 4 years
    4th point I think serial queue run on main thread and sync serial block the main thread.
  • Stephen Darlington
    Stephen Darlington over 4 years
    @ShauketSheikh No. The main thread is a serial queue, but not all serial queues are the main thread. In the fourth point, the main thread would block, waiting for another thread to compete its work. If the serial queue was the main thread you’d get a deadlock.
  • Muhammad Shauket
    Muhammad Shauket over 4 years
    @StephenDarlington it means we are not sure about serial queue run on main thread or background? it can be on main or not. but concurrent queue we are sure it run on background thread right? so better to use in this case concurrent queue. i am right?
  • Stephen Darlington
    Stephen Darlington over 4 years
    @ShauketSheikh If you create a queue, it’s not the main thread.
  • Enricoza
    Enricoza about 4 years
    Actually if you dispatch SYNC from the main thread, to some arbitrary queue (either serial or concurrent) you end up with running that code on the main thread. That's because GCD optimizes using the current thread in case of SYNC (unless you are dispatching from a bg queue TO the main queue). You can try it yourself by calling Thread.isMainThread from within the SYNC block. It will return true. Also it is actually safe to update UI in such block of code. Still remain true that you don't want to sync a minute long block SYNC from the main thread.
  • Enricoza
    Enricoza about 4 years
    Also, a quick note to @StephenDarlington last comment: if you create a queue with a targetQueue that is the main queue (or a queue that has the main queue in its target chain) you will end up using the main thread for that queue too. But I'm still trying to figure out if that's useful in some way.
  • ScottyBlades
    ScottyBlades about 3 years
    Yea. To follow up with Enricoza DispatchQueue.main.async { print(Thread.current) } will print main, runs contrary to this answer that "async - concurrent: the code runs on a background thread. Control returns immediately to the main thread (and UI). The block can't assume that it's the only block running on that queue" I think it should be "async - concurrent: the code runs on the specified thread. Control returns immediately to the current thread. The block can't assume that it's the only block running on that queue"
  • Stephen Darlington
    Stephen Darlington about 3 years
    @Enricoza @ScottyBlades You both make valid points, but they're edge cases that don't really address the question that was asked. Running SYNC from the main thread is not a good idea, precisely because it blocks. Not context switching to another thread seems like a reasonable optimisation. But you still shouldn't do it. Running main.async of course runs on the main thread. The question talks about creating new queues, so assuming you'd be using one of those is, I think, reasonable.
  • Enricoza
    Enricoza about 3 years
    @StephenDarlington I do think that you solved the OP doubts but I also do think that your oversimplification may lead someone in the wrong direction. GCD is one of the most misused frameworks of iOS, I'm sure a little specification on your (already very good) answer could help even more to solve part of the confusion.
  • JsW
    JsW about 3 years
    Well, regarding who is doing the thing, dad is more like a thread😂. It might be better to give the dad some dish-washing machine to dispatch works, so he can be a real queue.
  • Greg de J
    Greg de J about 3 years
    Probably the best answer ever written on StackOverflow ❤️