|
OS X
World
|
|
Programming
for OS X: Multithreading and When to Use It
|
©6-18-01 Mike Vannorsdel
One of the great features of Mac OS
X is its pre-emptive multithreading. This allows the
operating system to carry on multiple processes simultaneously.
Indeed a powerful ability, but it must be used with
care.
The idea behind multithreading is to
split a process into many separate pieces so that
the operating system has more options with distributing
CPU cycles. In Mac OS X, each process or application
has at least one thread, called the main thread.
Some applications will have more than one thread,
thus being a multithreaded application. More threads
give the operating system more options on how it will
compute data. It can let some threads receive more
CPU time than others, off load threads to additional
processors, or abort a thread's execution.
A thread can be thought of as a list
of instructions. These lists are ultimately sent to
the processor(s) where the instructions will be carried
out. With multiple processes running, and some with
multiple threads, there are quite a few lists of instructions
that need to be done. The operating system is responsible
for sorting these lists as to which ones need to be
done more than others. More important lists will be
allowed to use the processor longer than others. The
operating system will send the first list to the processor
for completion. Depending on the list's priority given
by the operating system, the current list may be stopped
by the operating system to allow for another list
to get processor time. This is known as thread switching.
Many times a second the operating system will switch
which thread is active and using the processor. More
important threads will average more time using the
CPU. When there are multiple processors available,
each processor may be working on a separate thread
at any one time. But never on the same thread simultaneously.
This effectively gets twice the work done (well almost).
Both Carbon and Cocoa applications can
be multithreaded. Carbon will use Multiprocessing
Services (as in Mac OS 9) and Cocoa uses the NSThread
class. Breaking off a new thread in Cocoa is quite
easy. Simply use NSThread's
detachNewThreadSelector:toTarget:withObject: method.
An example would be:
[NSThread detachNewThreadSelector:@selector(myFunction:)
toTarget:self withObject:someObject];
This will detach the function myFunction
in a new thread and pass it someObject. The new thread
is now independent from the main thread. This means
there's no guarantee that one will finish before the
other. When you're going to break off a new thread,
ideally the thread's work is independent and separate
from the other threads.
There are two common reasons for using
multiple threads.
1) You want the work to be done apart from the
interface.
2) You want to use the advantages of multiple processors.
The first reason would be used when
you don't want the interface busy while the application
is working. Doing lengthy work in the main thread
(which is also doing the interface's work) will cause
the interface to be unusable until the work has finished.
Doing the work in a separate thread will allow the
main thread to be free for other work. The second
reason would be used so that you can have multiple
pieces of work being done simultaneously.
However, multithreading has its limits
and there are some things you should think about before
using it. There is some overhead involved in an application
going from single threaded to multithreaded. There's
also a little overhead involved in creating each additional
thread. So only detach threads when there's enough
work to make it worth the overhead.
Another problem has to do with classes
that are not thread-safe. These are classes that shouldn't
be used in threads other than the main thread. This
is because they may give unexpected behavior or just
not work at all. As a rule of thumb, the App Kit classes
are not thread-safe, while the Foundation are. But
there are exceptions for each, read the class documentation
to be sure. This basically means you shouldn't interact
with the interface from any thread other than the
main thread. Though this may appear to work if you
try it, you'll find you have memory leaking or sporadic
results.
This brings me another thing you should
know about using multithreading with Cocoa. When you
detach a new thread, you need to create an auto-release
pool for that thread. If you don't, auto-released
objects will never be release (because there's no
pool to release it), and thus a memory leak. The main
thread is automatically given one so you never had
to worry about that. But threads detached by you need
you to add one. This is actually quite easy. Using
the example above, myFunction would look like this:
- (void)myFunction:(id)someObject
{
NSAutoreleasePool * localPool = [[NSAutoreleasePool
alloc] init];
...
//your code here
...
[localPool release];
}
The first line of the function creates
a local auto-release pool for the auto-released objects
to go. The last line releases the pool which in turn
released its auto-released objects.
Cocoa does offer inter-thread communications
via Distributed Objects. You can also throttle the
threads if one requires the other to be at a certain
point before continuing by using condition locks (NSConditionLock).
But if you find yourself using a lot of condition
locks or passing many objects and messages between
threads, you may want to rethink using multiple threads.
Use multithreading with care or your
application's performance will deeply suffer. This
is the exact opposite of its usefulness! So don't
get carried away and you'll produce a great programs
that take advantage of this technology.
Mike
Vannorsdel
Mike is the creator of "firewalk
X," a firewall for OS X, and "Univert,"
and conversion utility for So X. And who knows, maybe
more apps are on the way! ; )