Multithreading in Cocoa
NSThread and friends
There are hundreds of places to find short snippets of Objective-C code. Even examples using NSThread, Cocoa's Thread-spawning object. However, I could not find a single complete example, so I set out to figure it out myself (along with Objective-C) and document it so other people can save a few minutes. Or something. Anyway, here comes the amazing example:// main.m // // Created by Christopher Wright on 2007.06.12. #import <Foundation/Foundation.h> @interface MyObject : NSObject +(void)aMethod:(id)param; @end @implementation MyObject +(void)aMethod:(id)param{ int x; for(x=0;x<50;++x) { printf("Object Thread says x is %i/n",x); usleep(1); } } @end int main(int argc, char *argv[]) { int x; [NSThread detachNewThreadSelector:@selector(aMethod:) toTarget:[MyObject class] withObject:nil]; for(x=0;x<50;++x) { printf("Main thread says x is %i/n",x); usleep(1); } return 0; }When you compile from the command line, be sure to use
-framework Cocoa
, otherwise you're get unresolved symbol errors.
It's important to note that the thread method takes one argument. This is essential. If you leave it out, you'll get error messages whining about Selectors not being found.
This code will start a counter thread, and then run its own counter in main. It's fairly simple, doesn't do any locking, and doesn't pass messages. Because of these deficiencies, it's essentially useless. However, in a few days I hope to expand into the realms of NSLock and friends to show what more can be done.
Using NSLock
NSLock
is identical to a
mutex
. If you're not sure what a mutex is, head on over to
this article which discusses their functionality and usage in multithreaded applications.
An NSLock Object has 4 methods. They're quite simple to use, and their functionality is quite intuitive.
[myLock lock];
locks the lock. This prevents other threads that are trying to acquire the lock from continuing until the lock is released.
[myLock unlock];
unlocks the lock. This can only be done by the thread that locked the lock, and obviously allows other threads to acquire it.
[myLock tryLock];
attempts to lock the lock, returns NO if it fails, otherwise returns YES.
[myLock lockBeforeDate:];
attempts to lock until a specified date. NO on failure, otherwise YES.
Here's a simple example based on the code above that uses locks.
// main.m // // Created by Christopher Wright on 2007.06.12. #import <Foundation/Foundation.h> NSLock *lock; @interface MyObject : NSObject +(void)aMethod:(id)param; @end @implementation MyObject +(void)aMethod:(id)param{ int x; for(x=0;x<50;++x) { [lock lock]; printf("Object Thread says x is %i/n",x); usleep(1); [lock unlock]; } } @end int main(int argc, char *argv[]) { int x; lock = [[NSLock alloc] init]; [NSThread detachNewThreadSelector:@selector(aMethod:) toTarget:[MyObject class] withObject:nil]; for(x=0;x<50;++x) { [lock lock]; printf("Main thread says x is %i/n",x); usleep(10000); printf("Main thread lets go/n",x); [lock unlock]; usleep(100); } return 0; }In this example, the main thread holds the lock for a relatively long time, and then releases it for a while too. To see that to lock is working, we can check to see if any lines are output between "Main thread says..." and "Main thread lets go". Outside of these pairs it is not uncommon to see a few lines of output from the other thread, since at that point the lock is released, allowing the other thread to acquire the lock and run.
It's important to note that in normal Cocoa apps, you'll need to set up and
NSAutoreleasePool
for each thread. This is done in the thread itself, typically at the beginning of the thread function. If you omit this, you'll get lots of error messages in the Console, and, more importantly, you'll leak memory.
Thread functions can also be Instance methods (
-[myClass someThreadedMethod:]
), and in that case they're able to access the objects instance variables just as the main thread can -- this is where locking comes in handy, to keep everything consistent.
Using NSRecursiveLock
NSRecursiveLock
is an object that allows the locking thread to lock it multiplt times. Normally if a thread were to lock a lock it already held, it would block forever. Recursive locks get round this, which is handy for recursive functions that obtain locks.
They are used in exactly the same way as
NSLock
. Still working on that demo.
Leopard updates
With MacOS X 10.5 (Leopard), NSThread was enhanced and improved in several ways, making them much easier to use and work with. I'll try to whip something up eventually to demonstrate.MacOS X 10.6 added some assistive debugging methods, and the ability to set thread priority from other threads (previously, only a thread itself could change its priority).