A thread is a sequence of computer code that executes one statement at a time. It’s what we usually think of as “a computer program” and exactly what you’ve written so far in this book. Most of the time, your design andcoding are focused entirely on the main thread of your app. In fact, the term “your app” is nearly synonymous with the code executing on the main thread.
In a multithreaded environment, more than one thread can be executing concurrently. I’ve occasionally mentioned background threads and asynchronous methods throughout this book. Your app has even communicated withthem through delegate methods. But for the most part, these were activities that occurred “behind the curtain.” Which is, honestly, the way it should be. When properly designed, and correctly implemented, multitaskingenhances the power of your app without complicating it.
Sometimes, however, those complications invade your code. On the benefits side, multitasking is a powerful tool and there’s no reason you can’t use it in your app. For that, you’ll need to learn some basic concepts, and then prepare to expand your thinking into the fourth dimension.
Threads
A thread executes a single sequence of computer code. Multitasking or multithreading is a mechanism that executes two or more threads concurrently. Threads have a state. The truly interesting states
are running and sleeping. When a thread is running, it’s executing its code. When it’s sleeping, it does nothing.
In small microprocessors, the central processing unit (CPU) can only perform one operation at a time. So to be very literal, only a single thread is ever executing. The CPU periodically (a few hundred times a second) stopsexecuting the current thread, saves everything it knows about it, and starts to execute a different thread. This is called a task switch. It jumps from one thread to the other so quickly that it gives the illusion that all of themare running at the same time. Two threads can be described as running concurrently, but not simultaneously.
Many newer microprocessors have multiple cores. These devices contain the hardware for two (or more) complete CPUs on a single chip. In this environment, two threads literally execute simultaneously, each CPU running adifferent program. Both CPUs perform the same task-switching that single-core processors do, so all of the threads still appear to be running at once, but the CPU is accomplishing twice the work. As of this writing, multi-coreCPUs are just beginning to appear in small, portable, computer systems like iOS devices. By the time you read this, multi-core CPUs could be the norm.
Synchronization
Having the CPU executing two, three, or even twenty different threads concurrently certainly sounds wonderful. Think about how much work your app can do! The problem is how to coordinate that work so it doesn’t collide. Ifyou were the only person in the world with a car, there would be no
need for lanes, stoplights, or roundabouts. Add just one more driver to the road, and you better have some agreement about which one of you goes through the intersection first. And that’s exactly the kind of mayhem that awaitsyour app. Take this simple fragment of code:
if (singleton==nil)
singleton = [[MySingle alloc] init];
You’ve written code like this in a dozen places. As long as only one thread is executing this code at any one time, it works great. Now consider what would happen if two threads were executing this code simultaneously:
1. Thread A would test singleton and find that it is nil.
2. Thread B would test singleton and find that it is nil.
3. Thread A would allocate and initialize a new MySingle object.
4. Thread B would allocate and initialize a second MySingle object.
5. Thread A would store its MySingle object in singleton.
6. Thread B would overwrite the object Thread A just created with its instance of MySingle.
Two MySingle objects get created, the threads are using different objects, the reference to one object gets lost—it’s a mess.
To tame this confusion, programmers coordinate threads using a variety of means. The principle one is the mutual exclusion semaphore, or mutex for short. It grants one thread the right to a resource, and forces all other threadsto wait. Here’s an example of how it’s used:
@synchronized (self)
{
if (singleton==nil)
singleton = [[MySingle alloc] init];
}
The Objective-C @synchronized directive protects a block of code with a mutex. The mutex is used to allow one, and only one, thread to execute the block at a time. Here’s how it works:
1. Thread A locks the mutex. This is the first request, so thread A is successful.
2. Thread B tries to lock the mutex. The mutex is already locked, so thread B’s request is denied. Thread B is put to sleep—it stops executing.
3. Thread A checks the singleton variable, sees it is nil, creates a new
MySingle object, and stores it in singleton.
4. Thread A unlocks the mutex.
5. Unlocking the mutex wakes thread B, which (again) attempts to lock the mutex. It’s the only one requesting it now, so the lock is successful.
6. Thread B tests singleton and sees an object has already been created.
7. Thread B unlocks the mutex.
A hundred different threads could all be trying to execute this code at the same time, but it will still behave exactly as you intended. The mutex prevents any other thread from running this code, until the thread that’s currentlyexecuting it is done. This code is now said to be atomic. The word atomic comes from the Greek word atomos, meaning “indivisible.” The action performed by that code block cannot be broken up or interrupted.
iOS includes an arsenal of objects and functions to help coordinate and synchronize thread activity. Effective concurrent programming revolves largely around how, when, and when not, to use these tools. Before I get to them,let’s first talk about how you can run your code in multiple threads.
Running Multiple Threads
You’ve started code running in a separate thread, indirectly, throughout this book whenever you sent an asynchronous message. The UIWebView class’s -loadRequest: method creates a second thread of execution that loads theweb page’s content in the background.
Arranging for code you’ve written to run in a different thread is pretty easy too. You identify the code you want executed and request Grand Central Dispatch (GCD) to run it. For Objective-C programmers, the interface to GCD is the operation queue. An operation queue (NSOperationQueue) object manages an array of operation (NSOperation) objects, each encapsulating one executable task to be performed. You can create your own custom subclasses of NSOperation, or you can use concrete subclasses that you configure to execute a specific method or a block of code.
In the last chapter, I told you there was another way of deferring work to improve the responsiveness of the -imagePickerController:didFinishPickingMediaWithInfo: method. In this chapter you’re going to arrange for the image compression (UIImagePNGRepresentation) to execute on a separate thread. This will defer the work of compressing the image and allow your main thread to respond to the user’s touch event quicker.
Creating an Operation Queue
The first step is to create an operation queue. This queue needs to be accessible to all MyWhatsit objects,which will be using it to schedule work, so make it a property of the MSThingsDocument class. Start with the finalversion of MyStuff from Chapter 23. You can find that in the Learn iOS Development Projects ➤ Ch 23 ➤ MyStuff-2 folder. Select MSThingsDocument.h and add this property to the @interface section:
@property (readonly) NSOperationQueue* editOperations;
Now you need to implement this property. Switch to the MSThingsDocument.m implementation file and add an instance variable to the private @interface section (new code in bold):
@interface MSThingsDocument ()
{
NSFileWrapper *docWrapper; NSMutableArray *things; NSOperationQueue *editOpQueue;
}
Create a getter method for the property that lazily creates the queue by adding this method to the @implementation section:
- (NSOperationQueue*)editOperations
{
if (editOpQueue==nil)
editOpQueue = [[NSOperationQueue alloc] init]; return editOpQueue;
}
Your document object now provides an operation queue for use by any code that wants to schedule changes (edits) to the document, to be run asynchronously.
Adding an Operation
The one place you want to use the new operation queue is in the MyWhatsit object. Select the MyWhatsit.m implementation file, find the -setImage: method, and edit it so it looks like this (new code in bold):
- (void)setImage:(UIImage *)newImage
{
[_document.editOperations addOperationWithBlock:^{
imageKey = [_document setImage:newImage existingKey:imageKey];
}];
image = newImage;
}
When the user chooses a new image for an item, the -imagePickerController:didFinishPickingMe diaWithInfo: method will eventually send the MyWhatsit object a -setImage: message. Previously, this method would compress andstore the image in the document object
(using -setImage:existingKey:) before returning.
Now the code that stores the image data in the document is merely scheduled to execute at some later time. The -setImage: method has become an asynchronous method. It returns immediately and serious work happens in abackground thread.
The -addOperationWithBlock: message is a convenience method that creates an NSBlockOperation object, an operation object that executes a code block, and adds it to the operation queue. To add some other kind of operation,you’d first create a custom subclass of NSOperation, or instantiate one of its concrete subclasses (NSBlockOperation or NSInvocationOperation), and add it to a queue.
The documentation for the NSOperation class has extensive notes on how to subclass it.
There is very little about operation queues that you can configure. For the most part, you just add operation
objects to a queue and forget it. GCD will manage all the details, including creating the thread the operation
willexecute in (sometimes called “spawning a thread”). It also manages the number of concurrently running operations so it’s doing the most amount of work, but not so much that it bogs down your app’s main thread, a techniquecalled load balancing. You can also create dependencies between two or more operations. The operation queue ensures the operations that an operation depends on are executed first, before running that operation.
Measuring the Effects
Profile MyStuff again using the Time Profiler Instruments template. Just as before, add several new items, choosing images from your photo library. Each photo creates a CPU spike in the time profile graph, as shown in Figure24-1. As before, stop recording and isolate one of those spikes.
Figure 24-1. Time profile with multiple threads
Expand the call tree until you find the -imagePickerController:didFinishPickingMediaWithInfo:
method, also shown in Figure 24-1. You can see that the total time spent in the
-imagePickerController:didFinishPickingMediaWithInfo: method is only 329 milliseconds.
That’s less than a third of a second, which is a substantial improvement over the half-second it was taking in the last chapter. The response time to the user’s photo-picker tap is now well within our performance goals forresponsiveness, and almost three times faster than it was when you started.
If you look further down the call tree list, you’ll see other threads, as shown in Figure 24-2. Digging into those, you’ll find where the -[MSThingsDocument setImage:existingKey:] method executed
(it took 254 milliseconds). It still takes time, and CPU resources, to execute this code, but since it’s
running in its own thread, it doesn’t interfere with your app’s event loop, which means your app stays responsive.
Figure 24-2. -setImage:existingKey: running in another thread
Here’s what happened. The -addOperationWithBlock:
message created an NSOperation object and configured it to run the code block passed in the parameter. The operation object was added to the document’s operation queue.Grand Central
Dispatch will then determine the best time to begin executing the operation. The scheduled operation
might not run until after the photo picker method is finished (which most likely), or it might run in its entiretybefore the picker method finishes. There are very few guarantees when it comes to when threads execute, as I’ll explain shortly.
Execution Order
The concurrent operation you just added also introduced some bugs into your document handling. Not just one, but two, and there was a third that was already there. All of them have to do with the total lack of coordinationbetween the concurrent tasks running in your app—those cars driving around without any stoplights.
When you run multiple threads concurrently, it ridiculously complicates the possible execution order of your code. Consider two blocks of code named A and B. Before multitasking, there was only one possible execution order,shown in Figure 24-3
Figure 24-3. Execution order of a single thread
If the code in A and B are run in separate threads, the execution order can be any of those shown in Figure 24-4.
Figure 24-4. Possible execution order of two threads
The code could execute simultaneously (1). The B code could execute completely before the A code even starts (2), or vice versa. Portions of the A and B code could execute alternately (3). Portions of the A and B code could execute alternately, other sections could execute simultaneously, and at other times neither is executing (4). In extreme cases, the B code might not execute at all (5), or at least not start for a verylong time.
So how do you inject some order into this chaos? It’s possible to write your code so that it behaves rationally and predictably, while still reaping the benefits of concurrent execution. It just takes some careful planning and alittle practice.
Thread Safety
Thread-safe code behaves rationally, and predictably, when being executed from multiple threads concurrently. The first big bug you introduced into MyStuff is that its -setImage:existingKey: method isn’t thread-safe. There’snothing stopping the user from choosing several images rapidly,
which could add several operation objects to the operation queue. GCD may then elect to run two or more of those operations simultaneously, which means that multiple threads would be executing the
-setImage:existingKey: method at the same time. That would be a disaster.
There are many techniques for creating thread-safe code. Here are the big four:
1. Don’t use threads
2. Don’t share data
3. Share only immutable objects
4. Make concurrent actions and mutations atomic
Don’t Talk About Thread Safety
The preferred solution for creating thread-safe code is not to use threads. As you saw in the “Execution Order” section, the single thread solution doesn’t have any thread safety problems. It’s perfectly safe, completelypredictable, easy to write, and easy to debug.
If you can find a solution that doesn’t use threads, use it. In this book you’ve used timers, delegate methods, event handlers, notifications, and notification queues to divide up work and respond to events in a timely fashion—all on the main thread. Keep doing that. As long as all of your code is executing on the main thread, you have (by definition) no thread safety issues.
But not everything can be performed on the main thread. The biggest problem is code that takes a long time to execute. It will tie up the main thread, destroying its responsiveness, and may kill your app entirely if it takes toolong. For those problems, threads are the only solution.
The remaining techniques all deal with the problem of sharing data, which is at the nexus of all thread safety problems.
Not Sharing Is Caring
The second way to skirt around thread safety issues is to not share the same data. Almost all concurrency problems are caused by multiple threads trying to change the same data or objects simultaneously. You’ve alreadyseen a trivial example in the “Synchronization” section, earlier in this chapter. In short, any code that modifies something—sets variables, alters the contents of
collections, changes properties, and so on—that is accessible to a second thread, isn’t thread-safe.
So the second solution to writing thread-safe code is to not share any data. If the data in thread A is used and modified by thread A only, and the data in thread B is used and modified in thread B only, the code is implicitlythread-safe.
iOS apps are, themselves, an extreme example of this arrangement. Your iOS device is running several different apps right now (assuming it’s turned on). Each is running in its own thread. But each is also in a separate process; aprocess is an island and has no access to any data or variables in any other process. There are no thread safety issues between apps, because they have no shared data.
Of course, that also means the threads can’t communicate (they don’t share any data), which isn’t practical. One thread-safe solution is to hand off the data to the other thread, so that the threads are never using the sameobject at the same time. This is the technique used by the UIWebView object. Here’s what happens:
1. The main thread prepares an NSURLRequest object.
2. The main thread passes the NSURLRequest object to the -loadRequest: method.
3. The -loadRequest: method makes a copy of the NSURLRequest object and starts a background thread.
4. The background thread uses its copy of the URL request to send the web page request and collects the response from the server in an NSData object.
5. The background thread terminates.
6. The NSData collected by the background thread is passed back to the main thread.
At no point did the main thread and the background thread use, or even have access to, the same objects. The background thread used a copy of the NSURLRequest object, made before the thread started. The main thread can do whateverit wants with its original copy; it doesn’t affect the copy used by the background thread, and vice versa.
Similarly, the NSData object was accessible only to the background thread while it was being constructed. Once finished, the background thread handed the object to the foreground (main) thread at which point it didn’ttouch it again.
This simple, sequential hand-off technique means that neither the background thread nor the main thread has any thread safety issues to worry about.
Promise Me You’ll Never Change
There’s one class of objects that can be safely shared by multiple threads: immutable objects.
The properties of immutable objects can never change, so they can be safely used and accessed by any number of threads concurrently.
This includes the immutable base classes for strings (NSString), numbers (NSNumber, NSValue), collections (NSArray, NSDictionary, NSSet), and bytes (NSData). For collection objects, it’s important that
not only thecollection object is immutable, but that the objects in the collection are also immutable.
The Atomic Age
OK, so your code can’t execute on the main thread. Your solution is to create a second thread that must share objects and send messages that mutate data. In this situation, you need to make your code thread-safe. This largely consists of making your methods and properties atomic. Atomic code executes in its entirety before any other thread or process can interrupt it or execute it again.
You have a wide selection of tools to choose from, but most thread synchronization is accomplished using the mutual exclusion semaphore. It’s so popular, there are nearly a dozen different kinds
to choose from. For the Objective-C programmer, the simplest to use are the @synchronized
directive and the various NSLock classes. Let’s start by using @synchronized to make the
-setImage:existingKey: method atomic.
Creating an Atomic Method
Select the MSThingsDocument.m file and locate the -setImage:existingKey: method. Rewrite it so it looks like this (new or modified code in bold):
- (NSString*)setImage:(UIImage *)image existingKey:(NSString *)key
{
NSString *newKey = nil;
@synchronized (docWrapper)
{
if (key!=nil)
{
NSFileWrapper *imageWrapper = docWrapper.fileWrappers[key]; if (imageWrapper!=nil)
[docWrapper removeFileWrapper:imageWrapper];
}
if (image!=nil)
{
NSData *imageData = UIImagePNGRepresentation(image); newKey = [docWrapper addRegularFileWithContents:imageData
preferredFilename:kImagePreferredName];
}
}
[self updateChangeCount:UIDocumentChangeDone]; return newKey;
}
You added a mutex around the block of code that modifies the docWrapper object. This code is now atomic; only one thread can execute it at a time. Every atomic block of code that changes the object should leave it in a stablestate, ready to receive the next message or change. In this specific case, all of the logic to either add, remove, or replace a data file wrapper in the document is completed without interruption. When it’s finished, the next action thatmight need the docWrapper object can safely proceed.
The @synchronized directive is easy to use because it creates the mutex object for you. The object in parentheses is not the mutex; that object is used merely to identify the mutex, and is called the token. For a mutex towork, both threads must refer to the same mutex, so picking your token is important. The most commonly used token is self:
@synchronized (self)
{
...
}
This mutex prevents multiple threads from executing this code for the same object. It also prevents any other code, similarly protected, from executing at the same time, on the same object. It does not prevent two threads fromexecuting this code for different objects.
It doesn’t matter what object you choose for the token. What’s important is that the token is related to the data being mutated; any two threads attempting to mutate the same data must refer to the same token. Because ofthis, I chose the docWrapper object, because that’s the shared object being changed. You can’t get more related than that. You could have used self in this situation—it would have been just as effective.
Creating an Atomic Property
I told you that scheduling even a tiny bit of code to execute in its own thread complicates your job.
Return to the MyWhatsit.m file. The code block in -setImage: looks simple, but it’s fraught with complications:
imageKey = [_document setImage:newImage existingKey:imageKey];
Sticking this code into an operation queue means that it could execute at any time. It also means that the relationship you previously assumed between the image and imageKey variables has evaporated, specifically:
n The imageKey variable could be set at any time (by the second thread). Code
that refers to the imageKey variable more than once could read one value the first time and a different value the second time.
n The imageKey variable may, or may not, be set when -setImage: returns.
n At any given moment, the imageKey variable is no longer guaranteed to be the image tag of the image variable.
To fix MyWhatsit, the imageKey variable needs to be made atomic. It’s being referred to, and modified by, multiple threads and must now be made thread-safe. Start in the MyWhatsit.h file and change the declaration for theimageKey property (modified code in bold):
@property (atomic) NSString *imageKey;
Switch to the MyWhatsit.m file. Locate the existing -imageKey getter method and add an atomic setter method:
- (void)setImageKey:(NSString *)key
{
@synchronized (self)
{
imageKey = key;
}
}
Now, any code that sets the imageKey property will do it in the thread-safe manner.
The first method to rewrite is the -image method. Change the method so it looks like this (new code in bold):
- (UIImage*)image
{
@synchronized (self)
{
if (image==nil && imageKey!=nil)
image = [_document imageForKey:imageKey];
}
return image;
}
This code is now thread safe because it obtains a lock on the object before evaluating the imageKey variable. Remember that imageKey could change at any time, so it’s possible that the valueKey in the if statement will bedifferent than the valueKey passed to the -imageForKey:.
The mutex prevents that from happening. Any code that would try to set the imageKey property (using the
setter you just wrote) between the if and the -imageForKey:message
would stop and wait for this method to finish. Are you beginning to see how this all works together?
Lastly, the -setImage: code needs to change once more (new code in bold):
- (void)setImage:(UIImage *)newImage
{
[_document.editOperations addOperationWithBlock:^{
self.imageKey = [_document setImage:newImage existingKey:imageKey];
}];
image = newImage;
}
The only change is how the imageKey property is set. It now goes through the thread-safe setter method. When the background task ultimately gets around to setting the imageKey property,itcoordinates that change with any other threads currently using that value.
You only made the setting, and use of, the imageKey property atomic. You didn’t change any of the other
MyWhatsit properties. That’s because the imageKey property is the only value that could be modified from anotherthread. If you later wrote code to set the name or location properties from another thread, you’d need to repeat this analysis and take steps to make those properties
thread-safe too.





No comments:
Post a Comment