iOS GCD: Difference between any global queue and the one with background priority (DISPATCH_QUEUE_PRIORITY_BACKGROUND)?

15,043

Solution 1

If you have many background tasks, the CPU or CPUs of your device will be shared between all of them. Most of the time that is the right thing to do. If a task takes too long to finish, you solve the problem by trying to make it more efficient.

In very rare cases, you may have a task that takes a long time, but it is Ok to wait for it. So you give it BACKGROUND priority. If there is any work to do at NORMAL priority, that work will be done first, and only when there is a spare CPU doing nothing else, the BACKGROUND task will be performed. And there is the queue with HIGH priority; tasks in that queue will be executed first; you would do that if one specific task needs to be finished as quick as possible even if it means that other tasks are delayed.

From a point of view of your programming logic, all three queues are identical. It just affects which tasks the OS tries to finish first, and which it doesn't care about that much.

Solution 2

This is explained pretty well in the dispatch/queue.h header:

DISPATCH_QUEUE_PRIORITY_HIGH Items dispatched to the queue will run at high priority, i.e. the queue will be scheduled for execution before any default priority or low priority queue.

DISPATCH_QUEUE_PRIORITY_DEFAULT Items dispatched to the queue will run at the default priority, i.e. the queue will be scheduled for execution after all high priority queues have been scheduled, but before any low priority queues have been scheduled.

DISPATCH_QUEUE_PRIORITY_LOW Items dispatched to the queue will run at low priority, i.e. the queue will be scheduled for execution after all default priority and high priority queues have been scheduled.

DISPATCH_QUEUE_PRIORITY_BACKGROUND Items dispatched to the queue will run at background priority, i.e. the queue will be scheduled for execution after all higher priority queues have been scheduled and the system will run items on this queue on a thread with background status as per setpriority(2) (i.e. disk I/O is throttled and the thread's scheduling priority is set to lowest value).

And keep in mind this is a global queue. Other things, like system frameworks, may be scheduling in to it. It's very easy to starve the priority bands - if there are a lot of DISPATCH_QUEUE_PRIORITY_HIGH tasks being scheduled, tasks at the default priority may have to wait quite a while before executing. And tasks in DISPATCH_QUEUE_PRIORITY_BACKGROUND may have to wait a very long time, as all other priorities above them must be empty.

A lot of developers abuse the global concurrent queue. They want a execute a block, need a queue, and just use that at the default priority. That kind of practice can lead to some very difficult to troubleshoot bugs. The global concurrent queue is a shared resource and should be treated with care. In most cases it makes more sense to create a private queue.

A concurrent queue is not asynchronous, it is concurrent. Synchronous tasks can still be scheduled into it, and they will still execute synchronously. Concurrent queues, like serial queues, dequeue in FIFO order. They execute blocks concurrently, unlike serial queues. Concurrent and asynchronous are not the same thing.

Also keep in mind that if the main thread is idle, a concurrent queue can re-use that thread - and in fact will prefer doing that to creating new threads. Using a concurrent queue will not guarantee you will not block the user interface:

Blocks submitted to these dispatch queues are invoked on a pool of threads fully managed by the system. No guarantee is made regarding which thread a block will be invoked on; however, it is guaranteed that only one block submitted to the FIFO dispatch queue will be invoked at a time.

GCD makes no guarantees about what thread will be used to execute a block on a concurrent queue. If you use the main queue, the block will be executed serially on the main thread. A concurrent queue can use any thread, and as an optimization will prefer to use existing threads. It will only create a new thread if no threads are available to be reused. And in fact the main thread is often it's first choice (if the main thread is available for work) because it is "warm".

To reiterate: With Grand Central Dispatch you can be certain that a task will execute on the main thread (by submitting to the main queue). You cannot be certain that a task will not execute on the main thread.

Share:
15,043
Nirav Bhatt
Author by

Nirav Bhatt

iOS Developer: All apps. Professional Website CV SOreadytohelp

Updated on June 07, 2022

Comments

  • Nirav Bhatt
    Nirav Bhatt almost 2 years

    I am reading Concurrency Programming Guide and things confuse me.

    I see a lot of code invoking the following for any background task:

    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    Now what I mean by 'background' is the popular meaning:

    Something that gets executed anywhere other than the main (UI) thread

    So following the docs, the above statement returns any non-main-thread queue with differing priorities.

    My question is - why does then DISPATCH_QUEUE_PRIORITY_BACKGROUND exist? Lately I also see many async tasks using DISPATCH_QUEUE_PRIORITY_BACKGROUND specifically to perform background tasks.

    Aren't queues returned with DISPATCH_QUEUE_PRIORITY_DEFAULT, DISPATCH_QUEUE_PRIORITY_LOW or DISPATCH_QUEUE_PRIORITY_HIGH run very much away from main thread, if they are being returned using dispatch_get_global_queue?

    Aren't they background queues? What specific purpose does a queue returned with DISPATCH_QUEUE_PRIORITY_BACKGROUND serve? I already referred to this but it does not clarify much, other than the popular meaning I mentioned above.

    I am sure I am pretty confused with words - background and background queues. If someone can explain (better, graphically) - will be a great help.

  • Nirav Bhatt
    Nirav Bhatt almost 10 years
    +1 great answer. At the same time your last paragraph confuses me. Concurrent queue using main thread? How? And if so, what are other ways to cause this (so that we can prevent it)?
  • quellish
    quellish almost 10 years
    Updated my answer with another quote from the documentation.
  • Nirav Bhatt
    Nirav Bhatt almost 10 years
    +1 for background priority thing. This tells that 'background' is something async (non-main thread) thing when talking in context of an app. But it means a queue with 'background priority' when talking in context of overall system.
  • Nirav Bhatt
    Nirav Bhatt almost 10 years
    Speaking of which, I suppose it could be a good idea to think of global queue as a table of 4 columns and any number of members in each column - each column representing a priority, with HIGH getting first chance and so on? As I said, I am thinking of a good picture that would represent the whole idea.
  • Rob
    Rob almost 10 years
    @quellish +1 As an aside, I find it surprising that a concurrent queue would use the main thread as its first choice (and is a much stronger claim than is referenced in that quote). It strikes me that if that were true, a concurrent queue with a bunch of time-consuming tasks would have a high probability of adversely affecting the UX (largely defeating the purpose of adding those tasks to the concurrent queue in the first place). Can you share the reference that outlines the fact that concurrent queues use the main queue as its preferred thread?
  • Rob
    Rob almost 10 years
    I wonder if you're referring to the special case of dispatch_sync, which, as the documentation says, "As an optimization, this function invokes the block on the current thread when possible." Thus, if you do this from the main thread, that means the dispatched block may run on the main thread. But dispatch_sync is the only special situation that I know of in which the optimization can result in its running on the current thread rather than some worker thread.
  • quellish
    quellish almost 10 years
    No, I am not referring to dispatch_sync. I am referring to how the system chooses which thread to use for a concurrent block. Again, GCD makes no guarantees about what thread will be used to execute a concurrent block. The system will not create a new thread unless it has to, and the current thread can be used as a worker thread. You are referring to the preference to execute synchronous tasks on the current (calling) thread.
  • das
    das almost 10 years
    No, aside from dispatch_sync from the main thread, GCD will never run anything on the main thread that is not submitted to the main queue or to a queue targeting the main queue.
  • Rob
    Rob almost 10 years
    Agreed. I, too, question the claim that a GCD concurrent queue (which could be dispatching slow, synchronous blocks) will use the main thread "as its first choice". The "no guarantee" quote you reference does not say that it would show any preference for the main thread (much less use it at all). I'll ask again: Can share some reference or provide some mcve that substantiates this extraordinary claim of concurrent queue's preference for the main thread?
  • quellish
    quellish almost 10 years
    Again, I will refer you to the source. libdispatch only cares about which particular thread it is using when a block is being dispatched to the main queue. In all other cases all threads are created equal. The only thread it will not use is the manager thread, the thread assigned to manage the queue. When it needs a thread, it will reuse existing threads, reusing the most recently idle threads first including the "main thread". If no threads are available, it will create another for the pool. The documentation and the source are quite clear about this.
  • quellish
    quellish almost 10 years
    I do not see how this is "extraordinary" or a "claim". In all cases but dispatching to the main queue, "No guarantee is made regarding which thread a block will be invoked on". It does not get much simpler than that. It does not exclude the thread that dispatch_main() marked as the "main thread". Again, I encourage you to read the source. Read the libdispatch source. Read the source for the pthreads kernel extension.
  • Rob
    Rob almost 10 years
    Surely you can see how it's extraordinary to claim that when you dispatch something asynchronously to a queue other than the main queue, that there's a preference for it to still run that on the main thread. In years of GCD, I've never seen that. If it ever did run on the main thread, it could block the UI, knocking down the main pillar of concurrency programming. I'm not, quite frankly, inclined to pour through tons of source code to prove a contention which I don't believe, for which there is neither evidence nor documentary support nor corroborating opinions. Seriously, throw us a bone.
  • Rob
    Rob almost 10 years
    On the "no guarantee" quote, I read that simply as "just because a given task dispatched to a queue used a particular thread, there's no assurance that something else dispatched to the same queue will use the same thread." That's a far cry from "and by the way, GCD will prefer to run those tasks on the main thread."
  • quellish
    quellish almost 10 years
    No, I do not see how it's extraordinary - it is completely in line with the contracts for both libdispatch and the thread library it uses on iOS and MacOS. Again, libdispatch only makes guarantees about threads when using the main queue. In all other cases, it can use any thread. You should not perform a task on a queue thinking it could never be run on the main thread. I actually did learn about the preference for the main thread by seeing it happen in an app. It happens regularly on the Apple TV.
  • das
    das almost 10 years
    I think you are confused about what the source code means. The situation where dispatch_main() is called is quite different from the situation typical in an app where the main thread is using CFRunLoop and the main dispatch queue is manually drained by the runloop. In that situation libdispatch can never "choose" the main thread to perform work on since it has no direct control over it. When dispatch_main() is called, the main thread actually exits, and after that point it is true that any workqueue thread can be used to perform the work on the main queue. That would never happen in apps.
  • quellish
    quellish almost 10 years
    "Programs must call dispatch_main() at the end of main() in order to process blocks submitted to the main queue. The dispatch_main() function never returns." On iOS, UIApplicationMain does the equivalent. Calling dispatch_main from another thread will throw an assertion. This is why you see "When dispatch_main() is called, the main thread actually exits". You can see an example of the correct use of dispatch_main in the libdispatch tutorial on project wiki.
  • das
    das almost 10 years
    I'm sorry but you are mistaken, the way the main thread runloop (i.e. UIApplicationMain) drains the main queue is different from the mechanism used once dispatch_main is called, and in the former the main thread is not available for use by any queue other than the main queue or queues targeting the main queue (directly or indirectly).
  • quellish
    quellish almost 10 years
    You're absolutely correct, and that was my meaning - that UIApplicationMain replaces the role of dispatch_main for an iOS application. It manages the main queue, and is implemented differently from dispatch_main. It's not easy to stuff a lot of information in a comment.
  • Prashant Tukadiya
    Prashant Tukadiya about 8 years
    whats happening in concurrent queues with operations of same priority. ??
  • Victor Choy
    Victor Choy over 7 years
    dispatch_main docs says, Executes blocks submitted to the main queue. This function "parks" the main thread and waits for blocks to be submitted to the main queue. Applications that call UIApplicationMain (iOS), NSApplicationMain (macOS), or CFRunLoopRun on the main thread must not call dispatch_main.