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.
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
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
So you want to ask yourself: Is this work actively involved
in updating the UI
when considering whether something should
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.
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 [inaudible] 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.
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
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
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.
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.