Streaming is available in most browsers,
and in the WWDC app.
-
Building Responsive and Efficient Apps with GCD
watchOS and iOS Multitasking place increased demands on your application's efficiency and responsiveness. With expert guidance from the GCD team, learn about threads, queues, runloops and best practices for their use in a modern app. Take a deep dive into QoS, its propagation and advanced techniques for debugging your GCD-enabled app.
Resources
Related Videos
WWDC21
-
Download
ANTHONY CHIVETTA: Good morning and welcome to building responsive and efficient apps with GCD. We're so excited to see so many of you here interested in learning about how Grand Central Dispatch can help you adapt your application to all of our platforms. I'm Anthony and my teammate Daniel will be presenting this talk with me.
Grand Central Dispatch or GCD is a technology that was introduced with OS X Snow Leopard. At that time our brand new Mac was the MacBook Pro with the Core II Duo. One of the selling points of GCD at the time was that it would allow you to take advantage of both cores, running different parts of your application concurrently, and make threading really, really easy. We think that use of GCD has really stood the test of time.
Today our top-of-the-line Mac Pro has many, many more Cores and GCD is still a great way to take advantage of all of those computing resources.
But just as GCD is a great way to use all the resources on the high end it can help your application adapt to smaller environments.
For example, the new MacBook that we recently released is the first paneless design. While this is an advantage in terms of size of the machine, it also presents unique challenges in terms of how we manage the thermal properties. I'll talk a little bit later about how your app can use GCD to run more efficiently in this environment.
We also have iOS 9 with new multitasking features. This is the first time your app had to run side-by-side, quite literally, with other applications on the system. GCD can inform the system what kind of work you're doing and better share resources between your app and the other apps present on the screen. Of course, Watch OS brings your code to our smallest platform.
GCD is a way that you can help the system know which parts of your code you should run in order to be able to have a responsive application on a device this size.
So a brief outline of what we are going to go through today. I'm going to start by introducing something called Quality of Service classes. This is an API that we released with iOS 8 and OS X Yosemite.
Daniel's going to come up and go through some design patterns for using GCD, and how to integrate QLS with these patterns.
I'll then go through some details about threads, queues, and run loops that can make using GCD easier. And finally, we'll conclude with a brief section on how to understand crash reports when you're using GCD.
But first a little bit of background.
So we have your awesome app. And you start executing the app, the user taps the icon, loads it from a finder.
We will begin executing your code in main and you'll have your initial main thread that every app starts with.
You call UI application main, or NSApplication main, and that's going to bring up a run loop on the thread and the Framework code. And then that thread is going to sit there waiting for events. At some point something is going to happen. Maybe you'll get a delegate method call out to your UI application delegate. At this point, your code begins to run and needs to do something; let's say it wants to read from a database.
You go out and, you'll access that file on disk.
That data will come back.
You'll update the user interface.
And then finally return control back to the Frameworks and continue waiting for events on the thread.
This works great until that read from the database takes a little bit of time. At that point on OS X you might see a spinning wait cursor. On iOS the app would hang and might even get terminated.
This is a poor and unresponsive user experience. And this is where GCD can come in and help make things a little bit easier. You'll get your delegate method of call out. But instead of doing the work immediately you can create a GCD queue, use dispatch async to move the work on to the cue.
Your code executes asynchronously with respect to the main thread.
When you have the data available, you can dispatch async back to the main thread, update the UI. Now, the advantage here is that while your work is happening on that GCD queue, the main thread can continue waiting for events. It stays responsive. The user continues to get a great experience, and everyone is happy. Now, I'm hoping this is a pattern that is familiar to all of you. We are not going to go through the details of how to accomplish this. If this is not familiar, I highly encourage you to head up to Presidio after this and check out the talk there which will go through the pattern in detail.
One thing you might have not thought about before, is we now have two threads that both want to execute code. Your main thread wants to handle new events and the GCD queue wants to execute the block you dispatched to it. And maybe we're only on a single core device.
In this case, which thread do we execute? This is where Quality of Service classes come into play.
As I mentioned this is a new API in iOS 8 and 10 Yosemite that we released last year.
We have four Quality of Service classes: user interactive, user initiated, utility and background.
These are ways you tell the system what kind of work you're doing, and in turn, it allows the system to provide a variety of resource controls to most effectively execute your code. When I say resource controls, what am I talking about? Our system has support for CPU scheduling priority, which threads do we run, in what order? I/O priority, how do we execute I/O with deference to other I/O in the system.
Timer coalescing, which is a power-saving feature.
And whether we run the CPU in through-put or in efficiency-oriented mode. Do we want to get the most performance, or do we want to execute the code in the most energy-efficient manner? In an ideal world we tune each of these configuration values for each platform or device and piece of code that's running, but obviously this would get out of hand quickly. There's a lot of values hard to tune correctly and a lot of platforms where your code can run. Quality of Service Classes are designed to be a single abstract parameter that you can use to communicate the intent and classification of your work. Rather than trying to tune all of these specific configuration values you simply say "I'm doing user initiated work" and the system will automatically pick the right values for that platform and device. So I mentioned we have four Quality of Service classes. Let me talk through them briefly and what they are used for.
The first is user interactive. This is the main thread. Imagine you have an iOS app, the user has their finger on the screen and is dragging. The main thread needs to be responsive in order to deliver the next frame of animation as the user is dragging. The main user interactive code is specifically the code needed in order to keep that 60 frames per second animation running smoothly.
So you want to ask yourself: Is this work actively involved in updating the UI when considering whether something should be user-interactive.
This isn't loading the content potentially of that scroll view. It is just drawing the new animation.
We talk about user initiated as being loading the results of an action done by the user. So this might be as I'm scrolling through scroll view loading the data for the next cells or if I'm in a photo or mail application and tap an email or photo, loading the full size photo or e-mail, those kinds of actions are what we talk about as user initiated.
The question is, is this work required to continue user interaction? It's helpful not to any about it as user initiated but user blocking. If the user can't continue to make meaningful progress with your application, user initiated is the correct class.
Utility is for those things that the user may have started, or may have started automatically, but are longer running tasks that don't prevent the user from continuing to use your app.
You want to ask yourself, is the user aware of the progress of this work? If you were a magazine app downloading a new issue, is something that the user can continue to use the app while it's happening. They can read old issues or browse around. You may have a progress bar and the user is aware of the progress. This is a great thing to classify as utility.
Finally, background is for everything else. The user is not actively watching. Any kind of maintenance task, cleanup work, database vacuuming would all be background.
And the question is basically, is the user unaware of this work? Now background work is interesting because you want to think about when you're doing it. I encourage you to take a look at the writing energy efficient code sessions from last year that talk about how to do background work effectively if your app has significant work in this class.
So I mentioned our new MacBook.
As I said, this is the first fanless Mac. In previous MacBooks that had a fan, as the machine is doing more and more work and generating more and more energy and therefore heat we can bring up the fan speed to help dissipate the heat faster.
The new MacBook is incredibly energy efficient. We don't need a fan to dissipate heat in most circumstances, but running this machine at full speed is still going to generate some energy we need to dissipate.
But our ability to dissipate heat is at a constant rate.
We have other techniques to make sure we can keep the enclosure of the machine at an appropriate temperature for the user.
Imagine you have an app using and you have work happening at all four Quality of Service classes.
You are driving the machine hard, using a lot of energy and we need to help control that amount of energy in order to keep the machine at a reasonable temperature.
Well, what we can do is we can start to squeeze the amount of work we are going to do at the less important Quality of Service classes. This allows us to manage the system's use of energy, keep the machine responsive, and make sure that there's no responsiveness issues visible to the user.
This way it's very important that you have your work correctly classified when running on a machine like this. I also mentioned iOS and the new multitasking features.
You can imagine in the old world we might have your app with its main thread and maybe it has additional dispatch thread running but now I bring up another app. And that app also is going to have a main thread.
And then I also have a Picture in Picture.
That has a thread decoding the video.
Okay, but I've only got two CPUs.
So if I have to use one to decode the video, what do I do with the next one? This is another place for Quality of Service classes can really help you out. Indicating to the operating system the Quality of Service of each thread we can correctly decide where to marshall those available resources.
So with that I'll hand things off to Daniel who will go through specific design patterns and how to apply Quality of Service to those patterns. DANIEL STEFFEN: Good morning. Thanks, Anthony.
So in this section we are going to look at a couple specific examples GCD design and how QoS applies.
The fundamentals we are going to look at for GCD and QoS are how QoS can be specified at the individual block level as well as on QoS has a whole and how dispatch async and related APIs propagate QoS automatically from the submitting thread to the asynchronously running block, and then how the system can resolve some priority inversions for you automatically. We won't have time to go into depth on the specific API calls. If you want more details I encourage you to look at the session from last year, "Power, performance and diagnostics: What's New in GCD and XPC", that you can see this on the developer website where we went into much more specific detail on how to use the APIs that were new last year.
So first example is the example that Anthony had earlier where we performed some asynchronous work and do some I/O on the GCD cue off of the main thread.
How does this example fit in with QoS, what are the appropriate Quality of Service classes to apply here? On the left-hand side we have the main thread, of course. As Anthony mentioned, this is where the UI rendering happens, this is where the event handling happens, the appropriate caller service call here is user interactive.
Nothing you have to do to get this. The main thread of the application comes up at this Quality of Service class. On the right-hand side of the screen, that's the asynchronous work not happening on the main thread. Obviously, we shouldn't be running user interactive. We're not doing the UI rendering here. But, say the user tapped on the document icon and is waiting for his document to open. The user is blocked in his progress with the app. He can still interact with the UI but can't do what he wants, which is edit the document or view the document. User initiated is the appropriate Quality of Service Class here.
So how do we achieve that with the GCD API.
It turns out you don't have to do anything, it will work automatically. But it's important to understand why that is. Let's look at that in detail. Everything starts with this initial dispatch async that works off the asynchronous work from the main thread.
As I mentioned in the previous slide, dispatch async does automatic propagation of Quality of Service from the submitting thread to the queue where you submit the block to execute. Now, in this case there's a special rule that applies which is that we automatically translate Quality of Service Class user interactive to user initiated. We do this so we don't accidentaly over-propagate the Quality of Service Class that should be restricted to the main thread and UI rendering. We can take advantage of that here, because that is exactly what we want. That is typically the case by having the automatic propagation run the block on the queue at Quality of Service Class user initiated.
Of course when you go back to the main thread to update the UI with the results this automatic propagation will also try to take place.
Because the user main thread is Quality of Service Class user interactive, that will take priority. It will not lower if you go to a thread that has some assigned Quality of Service Class like the main thread. Here we will ignore this propagated value and run the UI update block at the bottom at Quality of Service Class user interactive.
So we call this QoS propagation property inferred QoS.
And this is QoS captured at the time the block gets submitted to a queue and with the special rule that we translate user interactive to user initiated as mentioned.
This propagated Quality of Service is used if the destination where the block is submitted to does not have its own quality of service specified and does not lower QoS if you go to the main thread that has its own high Quality of Service assigned.
So next example is something that doesn't open automatically, long running job, say a long running calculation that dispatch asyncs it off the main thread so as not to interfere with the UI and it operates on a queue concurrently with the main thread and maybe updates the UI with the progress by asyncing back a block that updates some progress UI element.
What are the appropriate Quality of Service classes here? On the left-hand side you have user interactive, and the right-hand side as Anthony has described earlier, this is something where Quality of Service utility is appropriate. It's something long running. The user can continue to use the UI. He's not waiting for the results immediately and he can see the progress, and he probably initiated it in some fashion but it is not blocking his immediate progress.
So how do we make this happen with the GCD API? The simplest solution is to focus on the point where the work is generated, initiated.
This is initial dispatch async.
We can tag the block that we submit with the appropriate Quality of Service Class by using the dispatch block create with QoS class API, we pass in the block that we want to execute as well as the Quality of Service Class that we would like, utility here. The resulting block object is what we pass through dispatch async and when that gets executed that will run at Quality of Service Class utility and by finding this initial generation point where the Quality of Service Class changes, the automatic propagation further downstream, if any from that block, will then be able to take advantage of the automatic propagation. So that anything that gets generated asynchronously from that work will happen automatically at the correct Quality of Service class, continue to be a utility without you having to do anything. So this block QoS is created like we saw in the previous slide by adding an explicit QoS attribute to the wrapped up block object to the block you provide at the appropriate time to use this is at the point when work of a different class is generated.
Another use case for QoS in a block object is if you have needs to capture the Quality of Service class in a block that you are provided, say in a callback block scenario where you are writing an API where somebody provides you with a callback block and you want to store that block and submit it later from a different thread or queue, but you really want to get this propagation right the same way dispatch async propagates the Quality of Service. You can do this by using the dispatch block assign current flag, pass that through dispatch block create, and that will capture the current QoS and execution state, store it in a rapid block and when you submit that block later on to a queue it will run with that assigned value.
To look at another example, we have an application that performs a UI action. And during the performance of that action it notices some maintenance condition, some cleanup condition that should happen. Say you have a database, it has too many loose objects, has to do some compaction, some clean up task, typical example again of the use of GCD where you would execute dispatch async to run that maintenance task on a background queue and the left-hand side, of course, being the user initiated -- user interactive again. The appropriate Quality of Service class here would be to use Quality of Service background.
Currently given by the title of the slide, this is a maintenance operation that is unrelated to what the user is doing. You notice this condition while you were going along and the user is unaware that this is occurring. You are kind of doing work, the app does work on its own behalf here. How do we achieve running Quality of Service background? One thing we can do is to use the block API we saw earlier and have the initial async to this queue be the Quality of Service background. Maybe this is a case where there is multiple places in the app that generate this type of clean up work and there's multiple ways that you need to operate on the database in this mode. It might be appropriate to have a queue specifically dedicated to this task so you can also create queues with assigned Quality of Service. You use dispatch queue attr make with QoS class, passing in background in this example. The resulting attribute can be passed to dispatch queue create to create a clean up queue in this example here.
By having Quality of Service class assigned to the queue, you get the automatic propagation for dispatch async, again user initiated from the main thread we in fact ignore this propagated value because you are submitting to a queue that has its own assigned Quality of Service Class and use what the queue says instead. So the block that you submitted will run at background instead of what it would have otherwise if it had the automatic propagation.
For a case like this where there is a maintenance task unrelated to the execution flow, it may be appropriate to consider whether the dispatch block detached flag is of use.
This is a way to tell the operating system that the work that you are doing in this block has nothing to do with the flow of execution. And it will in particular opt out of propagation of QoS but also opt out of capturing things like the activity ID if you are using that for the activity tracing feature that we introduced at the conference last year.
And other properties of the execution context.
Now, of course, even if you have work that should always kind of be at Quality of Service background that is clean up work, there may be exceptions. Maybe there is some kind of log out feature where the user logs out of his account and you have to delete the database and delete the user's private data. That's something that the user want to see complete. This is not a background task. You may have a need to override or opt out of this queue or this runs at background property that you have set up here.
If you just use the automatic propagation feature here, as before we would ignore the user initiated Quality of Service except here, of course, that's the right Quality of Service to use. That's what we really want to happen. The user is waiting for this log out to complete.
How to achieve that? Use the dispatch block enforce QoS class flag, block create along with the block you want to execute. That indicates to the system that you really want the value that's in the block as opposed to the one that's in the queue. So you can override the queue's value display.
If you do that, the block will get executed at Quality of Service class user initiated in this example.
But of course, here in the picture you can see now we have a case where you have several queue with potentially two blocks in queue at the same time at different priority levels.
That's the case of asynchronous priority inversion. A High QoS block may be submitted to a serial queue, but there is already work in queue or running at a lower Quality of Service and you have a priority inversion.
GCD helps you with that if you use a serial queue by raising the work that is already there, running or enqueued until you reach the high Quality of Service block.
This is something that happens behind the scenes with QoS override. It is not something that the over-written blocks can see themselves or if they propagate work further along asynchronously they will propagate at the original QoS that they were as, but they will actually be running at a higher priority to resolve the inversion.
So to recap, queue QoS is mostly appropriate for queues that are single purpose in the app or take inputs from all over the place where you don't want the priority of the submitted to be important, but the priority of that purpose and it may also be appropriate to use the detached block API for such types of workload, especially if they are maintenance or background.
And using QoS on the queue causes us to ignore the QoS in the asynchronous blocks exempt in the case where you also use that enforce flag.
Last example is use of serial queues as locks. This is a very common use of GCD where you have some shared data structure in the app where you want locked access to that data structure and you can use GCD by creating a serial queue with a dispatch queue serial flag at the data structure and then use dispatch sync to execute a critical section block on that queue where that block has exclusive access to the data structure.
How does QoS fit into this? It is important to note that dispatch sync, it executes the block when that lock is obtained on the thread that calls dispatch sync and releases the thread with the block returns. In this case we don't need any additional threads, we have a thread called dispatch sync and we will execute that block at the Quality of Service of the calling thread, user interactive here.
Of course you have synchronization because you have other queues or threads accessing this data structure. Maybe you have a Quality of Service utility thread also calling dispatch sync on this queue to get the exclusive access to the data structure. Again, the same thing will happen if he comes in later he will block waiting to get the exclusive access. Then execute that block on his own thread at Quality of Service utility at the calling threads Quality of Service.
Now, of course in this, if this acquisition of this exclusive access happened in the other order we would again have a case of priority inversion. If the utility guy comes in first and takes the lock, you have the main thread waiting on a utility thread.
And that's obviously undesirable. So Quality of Service inheritance, synchronous priority inversion will help you with that. A high priority service thread waiting for the lower course work, we'll resolve that by raising the Quality of Service of waited on work for the duration of the waiter and this happens if you use a serial queue with the dispatch sync or the dispatch block wait APIs.
It also happens if you use pthread mutex lock or any APIs built on top of it such as NSLock. It is important to note, there are APIs that do not do this. Dispatch semaphores do not admit a concept of ownership so the system cannot determine who will eventually signal the semaphore. There cannot be any resolution of priority inversion in that case. If you have priority inversions where you find high priority waiter in a dispatch semaphore wait, where a low priority worker is performing some work, you may have to switch to dispatch block wait, where you can wait on an explicit entity that we can raise. With that I hand it back to Anthony to talk about queues, threads and run loops.
ANTHONY CHIVETTA: Thanks, Daniel.
Hopefully that whet your appetite for Quality of Service and you'll go back and take a look at the applications and how you can adopt Quality of Service. I want to go through now other topics around queues, threads and run loops in the hopes that it might make a greater adoption of GCD easier for you and help provide a little context as you debug your application. To remind ourselves we have our application with our main thread. There's a GCD thread pool that services all blocks you might put on GCD queues and you have some set of queues in your application.
Imagine on the main thread you execute this code. You dispatch async on to some queue.
A block, and we will bring up a thread for that block.
Start executing your code. We'll execute performSelector withObject afterDelay, and that will put a timer source on the current thread's run loop. Now what do we think happens a second later? Well, it turns out when that block completes the thread might just go away. These are threads from our ephemeral thread pool. They don't have any guaranteed lifetime. We may destroy the thread.
Of course even if the thread stays around no one is actually running that run loop. That timer is never going to be able to fire.
This is sort of an interesting interaction that can happen as you mix run loop based and dispatch queue based APIs. So kind of -- to kind of briefly summarize the differences between run loops and serial queues, run loops are bound to a particular thread.
As an API you generally see them get delegate method callbacks.
They have an auto release pool that pops after each iteration through all the run loop sources and run loop can be used reentrantly, it's possible to respin the run loop from the call out on that run loop. On the other hand, serial queues or dispatch queues use ephemeral threads that come from our Grand Central Dispatch thread pool.
They generally take blocks as their callbacks, or APIs that use them generally take blocks as callbacks.
The autorelease pool on a serial queue will only pop when a thread goes completely idle.
This could never happen if your application is constantly busy.
So it's important to not rely on the autorelease pool that comes for free when you use dispatch. If you are going to be auto releasing a lot of objects, make sure you have your auto release pools in place. Finally, serial queues are not a reentrant or recursive locking structure. You want to make sure as you're designing your application's use of queues, you don't run into a case where you need to use them reentrantly.
These rules are bound together in the sense that main thread's run loop is also exposed as the main queue. It is easy with respect to the main thread to jump back and forth between these worlds. So if you go back to the timer example, we kind of compare the APIs. For run loops we have things like NSObjects performSelector withObject afterDelay.
Or NSTimer scheduledTimerWithTimeInterval that installs a timer on the current run loop.
In the dispatch world, we have things like dispatch after and dispatch sources with dispatch source set timer, which can create a timer that will fire by putting a block on a queue. Now, I mentioned that Grand Central Dispatch uses ephemeral threads, now let me talk you through how that works.
Imagine I'm executing and I do a whole bunch of dispatch asyncs.
I put those on the global queues.
The system is going to take a thread from the thread pool and give it to the first block. Send it running on its way.
Take another thread, give it to the second block and send it running on its way. Say we're a two core device, these threads are both running, actively executing. We stop here. We have one thread per core which is ideal.
Now, when that first block finishes executing we'll take the thread, give it to the next one, and so on.
And this works really well. Until one of our blocks needs access to a resource that isn't yet available.
We call this waiting. A thread will wait and suspend execution when it needs a resource such as I/O or a lock.
Now, you might also hear this referred to as blocking. I will call it waiting today. So if I talk about blocks blocking for the next five minutes you'll get confused but you'll hear it called blocking in many other contexts.
What is interesting from this from the GCD perspective, we want one block or one thread executing actively per core on the device.
So when a thread waits, we are going to bring up another thread in our thread pool up until some limit so that we can have one thread executing per core.
So let's imagine we have these four blocks, we're executing the first two on two different threads and the first guy goes hey, I need to do some I/O.
We go great, we'll issue the I/O to the disk. But we are going to have you wait for that I/O to come back. Then we are going to bring up another thread, execute the next block and so on as threads wait we'll bring up another thread to execute the next block on that queue while there's still work to be done. The problem here, if I only have four blocks, that works great. If I have lots of blocks and they all want to wait, we can get what we call thread explosion.
This is, of course, a little inefficient. There are lots of threads all using resources.
If they all stop waiting at the same time, we have lots of contention.
So this isn't great for performance. But it is also a little dangerous in that there is a limit to the number of threads we can bring up. When you exhaust the limit we have a problem of what do we do with new work that comes in? This brings in deadlock. I want to talk about an example of deadlock we saw in one of our applications internally. I hope I can talk you through this. This is a great example of how different parts of your application interact in unexpected ways. We have the main thread and the main thread has a bunch of work it wants to do. It will dispatch async a whole butch of blocks onto a concurrent queue.
We start bringing up threads for those blocks, and those blocks immediately turn around and dispatch sync back on to the main thread.
At this point we've brought up all the threads we are willing to. In the simple example here, it's four. We hit the thread limit. We are not going to bring up any additional threads for the thread pool.
Okay, fine. We need the main thread to become available again so that those blocks can acquire it, do their work and then complete. Now it put us back under our limit.
That might happen some some situations, but let's imagine that our main thread then goes into the dispatch async to a serial cue.
So far so good. That block will not execute yet because there's no additional threads available to execute that block. It will sit there waiting for one of the threads to return so we can use it again.
But then our main thread decides to dispatch sync to that same serial queue.
The problem is that there are no threads available for that serial queue. The main call is going to block forever. This is the classic deadlock situation. Our main thread is waiting on a resource.
In this case a thread from our thread pool. Where all the threads from the thread pool are waiting on a resource, the main thread. They're both waiting on each other and neither is going to give up that resource so we have a deadlock.
This may seem bizarre and contrived. But when you have lots of different parts of your appliation, different modules, frameworks all doing things at the same time, it's easier to hit than you might imagine and may be more complicated in practice. So it's something you want to keep in mind as you are working with GCD. To make sure you can avoid the situation. It is easy. I'll talk through some ways that we can architect our application to be resilient against this problem. First some basic things. Always good advice to use asynchronous APIs whenever possible, especially for I/Os.
If you do that, you can avoid having blocks wait and as a result you won't have to bring up more threads. Gain some efficiency.
You can also use serial queues for something like this. If we dispatched all that work on a serial queue we wouldn't have had thread explosion. We would have only executed one block at a time. I'm not telling you to serialize your entire application, but as you are building your application and creating different queues, service different modules in your application, unless you know that you need to run things in parallel for that particular module, in order to meet your performance goals, consider starting out with a serial queue instead.
There's a lot of performance to be gained from running parts of your application concurrently on serial queues. Then you can profile your application, see what needs the additional performance of running work in parallel and specifically design those pieces of work in ways that are well managed to avoid thread explosion.
Now, of course, you can also use NSOperation queues which have a concurrency limit.
And finally don't generate unlimited work. If you can design your work to be well bounded in terms of the number of blocks you need, that can avoid thread explosion.
Let's look through more specific code examples of things that went wrong here.
The first is that we mixed sync and async so if I just do a dispatch sync to a queue, it's really fast. It is basically taking a lock.
If I do a dispatch async, it's really fast, it's an atomic enqueue.
If I have a queue and I only do one of these, the performance will be similar to those primitives.
If I mix these and async to to a queue and do a dispatch sync, that dispatch sync has wait for a thread to be created and then execute that block, and then for the block to complete.
Now we have the thread creation time coming in to what really would have been just a lock. Now, it's absolutely safe to mix these primitives, but, as you design the application, think about whether you need to. Be especially careful when mixing them from the main thread.
Now, of course, the next problem is that we try to dispatch a lot of blocks on to a concurrent queue all at once.
If you do this, and in our case we are just going to try to continue executing off the main thread, but imagine you do something similar where you async and then do a barrier sync.
Either one is dangerous because it could cause thread explosion and deadlocks.
But we have a primitive called dispatch apply, and these two pieces of code here are basically exactly the same from the perspective of the semantics for you. By switching to dispatch apply, you allow GCD to manage the parallelism and avoid the thread explosion.
Of course, you can also use dispatch semaphores. Many of you are familiar with the use of semaphores as locks.
We are going to use the semaphore as a counting semaphore instead. We start out by initializing it with the number of concurrent tasks we want to execute. Say we want four running.
Every time our block completes, it signals the semaphore.
Every time we submit, we wait on the semaphore.
As a result our submitter thread will do four submissions and then block on that semaphore wait until one of them can complete and signal the semaphore.
This pattern is nice if you might be submitting from multiple places in your application. Something like dispatch apply isn't appropriate.
So hopefully that's helped you understand a little bit about thread explosion and how to avoid it. I also want to talk briefly about crash reports.
Unfortunately most of you probably had to deal with a crash report at some point. There's a lot of information here. This is especially true if you are using GCD. As you have more threads, there's more things to parse and to understand what's going on.
So I want to talk you through a couple stacks that can help you understand what the different threads in your application are doing. So the first one is the manager thread.
So the manager thread is something that you are going to see in almost all GCD using applications. It's there to help process dispatch sources. You notice dispatch manager thread is the root frame.
You can generally ignore it. We have idle GCD threads.
These are idle threads in the thread pool. You can see start work queue thread at the bottom of the stack. There's an indication it's a GCD thread. Work queue current return indicates it's sitting there idle. An active GCD thread, on the other hand, will still begin with start work queue thread but you'll see something like dispatch client call out and dispatch call block and release, followed by your code. You'll also see the dispatch queue name that you passed when you created the queue. It's important to give descriptive queue names. The main thread when idle you'll see sitting in mock message trap, CF run loop port and CF run loop run and you'll see com.apple.main thread. On the other hand, if your main queue is active, you might see CF run loop is servicing the main dispatch queue if it was active because of the main queue GCD queue. And in this case which have NSBlock operation that we're calling out. Now, of course, you shouldn't rely on things not changing. There are internal details and I'm walking through this today to give you a guide through your crash reports. Hopefully it will provide some useful context.
So with that to wrap things up, remember that an efficient and responsive application must be able to adopt to diverse environments, whether it's the Watch or Mac Pro. These different platforms have a variety of resources available. GCD is a great way to help you manage them appropriately. QoS classes allow the operating system to marshall your resources in the most efficient way.
So you should go home and think about how you integrate Quality of Service classes into your application and the existing uses of GCD. Finally, consider how your apps use GCD and try to avoid thread explosion.
For more information, check out the concurrency programming guide or the energy efficiency guides for Mac and iOS apps. The iOS app one is new this week. Check it out. It's great. We have the developer forums and Paul, our evangelist. A couple related sessions: Achieving all day battery life provides more context behind the energy topics I mentioned.
Optimizing your app for multitasking iOS 9, advanced NSOperations, and performans on iOS and Watch OS, just after this in Presidio. If those are new to you, I highly recommend that you check those out.
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.