Re: Threaded data freaking out
Re: Threaded data freaking out
- Subject: Re: Threaded data freaking out
- From: Chris Hanson <email@hidden>
- Date: Sat, 8 Mar 2003 21:51:14 -0600
spike asked for an overview of how locks are used in multithreaded programming.
Essentially, a lock controls access to a shared resource. In your
case, the shared resource is an NSMutableArray instance variable
containing the records you're displaying in a table view. Let's call
it "_foos". What you'll want to do is create another instance
variable alongside it called _foosLock that refers to an instance of
NSLock.
It sounds like you're implementing a table data source, with a
separate thread that adds data to it (the producer thread). The
easiest way to do this would be to encapsulate the interface to the
array in a couple methods. To add data to _foos, use a method like
-insertInFoos:
- (void)insertInFoos:(Foo *)value
{
[_foosLock lock];
[_foos addObject:value]; // CS1
[_foosLock unlock];
}
To get data out of _foos, use a method like -valueInFoosAtIndex:
- (Foo *)valueInFoosAtIndex:(unsigned)index
{
Foo *result = nil;
[_foosLock lock];
result = [_foos objectAtIndex:index]; // CS2
[_foosLock unlock];
return result;
}
(The above code should really take into account exception handling
too. I'm leaving it out for clarity.)
Essentially, anything that touches _foos needs to lock it and unlock
_foosLock around where it touches _foos. What this does is create a
"critical section." In the methods above, the critical sections are
CS1 and CS2.
Say your producer thread is in the middle of critical section CS1,
adding an object to _foos, when it gets preempted by the scheduler.
The next thread that runs just so happens to be your main thread,
which for some reason tries to re-display your table. The re-display
code will try to access _foos using -valueInFoosAtIndex:, and try to
acquire (lock) _foosLock. Since _foosLock is already locked, your
main thread will be blocked. Eventually your producer thread gets to
run again; when it does so it finishes adding an object to _foos, and
unlocks _foosLock. This means your main thread can be scheduled
again, and when it resumes it will acquire _foosLock and enter
critical section CS2. While it's in CS2, your producer thread will
be prevented from entering CS1. And so on.
There's also something a little more advanced called a condition
lock, implemented in Cocoa as NSConditionLock. For instance, you can
declare that condition 0 means an array is empty, while condition 1
means it contains data. Your producer thread will use the plain
-lock method to acquire the lock unconditionally, and whenever it
adds data will unlock the lock with condition 1. Your consumer
thread will use -lockWhenCondition:1 to block until the lock is in
condition 1; this means your consumer thread will *only* acquire the
lock when the array the lock is protecting contains data. Your
consumer thread can then unlock the thread into condition 1 if the
array still contains data, or into condition 0 if the array is empty.
Also, it's possible to try to acquire a lock and fail immediately
rather than block a thread while waiting for it.
A major consideration when writing multithreaded Cocoa code is that
by and large you *cannot* use AppKit from threads other than the main
thread. To get around this you either need to use multithreaded
programming techniques like command queues, or -- if your goal is to
run on 10.2+ only -- -[NSObject
performSelectorOnMainThread:withObject:waitUntilDone:]. Some people
might think it innocuous to do [_myTableView reloadData] in the
thread that manipulates the table's data source, but it really isn't.
Finally, make sure your array is properly retained. If it's
autoreleased and has a retain count of 1, it will go away after the
next run through the event loop (or whenever the autorelease pool
that contains it goes away). It's a common bug to write an init
method like so:
- (id)init
{
if (self = [super init]) {
_foos = [NSMutableArray arrayWithCapacity:0];
}
}
This is bad, because the array referenced by _foos will go away
whenever the autorelease pool that it's in goes away, which may not
be the same time your object goes away. You need to be sure your
array will last the lifetime of your object:
- (id)init
{
if (self = [super init]) {
_foos = [[NSMutableArray arrayWithCapacity:0] retain];
// could also write:
// _foos = [[NSMutableArray alloc] init];
}
}
Hopefully this has made some things a little clearer. Good luck with
your application! I think once you get the hang of it you'll find
that simple multithreaded programming with Cocoa -- for instance,
using producer and consumer threads -- isn't too tough and can
actually make your application design more straightforward.
-- Chris
--
Chris Hanson, bDistributed.com, Inc. | Email: email@hidden
Custom Application Development | Phone: +1-847-372-3955
http://bdistributed.com/ | Fax: +1-847-589-3738
http://bdistributed.com/Articles/ | Personal Email: email@hidden
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.