• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: Threaded data freaking out
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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.

References: 
 >Re: Threaded data freaking out (From: spike <email@hidden>)

  • Prev by Date: Re: [newbie] Updating an NSProgressIndicator in a for loop
  • Next by Date: Realtime output from NSSlider
  • Previous by thread: Re: Threaded data freaking out
  • Next by thread: RE: Threaded data freaking out
  • Index(es):
    • Date
    • Thread