Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[Applause]
>> 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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[Applause]
>> 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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on an explicit entity
that we can raise.
With that I hand it back to
Anthony to talk about queues,
threads and run loops.
[Applause]
>> 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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.