views:

67

answers:

2

I need to imlement in cocoa, a design that relies on multiple threads.

I started at the CoreFoundation level - I created a CFMessagePort and attached it to the CFRunLoop, but it was very inconvenient as (unlike on other platforms) it needs to have a (systemwide) unique name, and CFMessagePortSendRequest does not process callbacks back to the current thread while waiting. Its possible to create my own CFRunLoopSource object, but building my own thread safe queue seems like overkill.

I then switched from using POSIX threads to NSThreads, calling performSelector:onThread: to send messages to other threads. This is far easier to use than the CFMessagePort mechanism, but again, performSelector:onThread: does not allow the main thread to send messages back to the current thread - and there is no return value.

All I need is a simple - inprocess - mechanism (so I hopefully don't need to invent schemes to create 'unique' names) that lets me send a message (and wait for a reply) from thread A to thread B, and, while waiting for the message, allow thread B to send a message (and wait for a reply) to/from thread A.

A simple: A calls B re-entrantly calls A situation that's so usual on a single thread, but is deadlock hell when the messages are between threads.

+1  A: 

Have you looked into Distributed Objects?

They're generally used for inter-process communication, but there's no real reason it can't be constrained to a single process with multiple threads. Better yet, if you go down this path, your design will trivially scale to multiple processes and even multiple machines.

You are also given the option of specifying behaviour by means of additional keywords like oneway, in, out, inout, bycopy and byref. An article written by David Chisnall (of GNUstep fame) explains the rationale for these.

All that said, the usual caveats apply: are you sure you need a threaded design, etc. etc? There are alternatives, such as using NSOperation (doc here) and NSOperationQueue, which allow you to explicitly state dependencies and let magic solve them for you. Perhaps have a good read of Apple's Concurrency Programming Guide to get a handle (no pun intended) on your options.

I only suggest this as you mentioned trying traditional POSIX threads, which leads me to believe that you may be trying to apply knowledge gleaned from other OSes and not taking full advantage of what OS X has to offer.

Sedate Alien
While I would love to investigate one of the more interesting concurrency options (like GCD), for the moment I have to deal with an exsiting design that offsets some time consuming synchronous code off the UI thread, onto worker threads. I am working on getting the project owners to accept a larger refactor of the code base to support some more scalable ideas - for the moment I need an interthread "SendMessage" call that runs a CFRunLoop internally while waiting for the response.
Chris Becke
+2  A: 

use -performSelectorOnThread:withObject:waitUntilDone:. The object you pass would be something that has a property or other "slot" that you can put the return value in. e.g.

SomeObject* retObject = [[SomeObject alloc] init];
[anotherObject performSelectorOnThread: whateverThread withObject: retObject waitUntilDone: YES];
id retValue = [retObject retValue];

If you want to be really sophisticated about it, instead of passing an object of a class you define, use an NSInvocation object and simply invoke it on the other thread (make sure not to invoke the same NSInvocation on two threads simultaneously) e.g.

[invocation performSelectorOnMainThread:@selector(invoke) withObject:NULL waitUntilDone:YES];

Edit

if you don't want to wait for the processing on the other thread to complete and you want a return value, you cannot avoid the other thread calling back into your thread. You can still use an invocation e.g.

[comObject setInvocation: myInvocation];
[comObject setCallingThread: [NSThread currentThread]];
[someObject performSelectorOnMainThread: @selector(runInvocation:) withObject: comObject waitUntilDone: NO];

// in someObject's implementation

-(void) runInvocation: (ComObject*) comObject
{
    [[comObject invocation] invoke];
    [self perfomSelector: @selctor(invocationComplete:) 
                onThread: [comObject callingThread] 
              withObject: [comObject invocation]];
}

If you don't like to create a new class to pass the thread and the invocation, use an NSDictionary instead e.g.

comObject = [NSDictionary dictionaryWithObjectsAndKeys: invocation, "@invocation" [NSThread currentThread], @"thread", nil];

Be careful about object ownership. The various performSelector... methods retain both the receiver and the object until they are done but with asynchronous calls there might be a small window in which they could disappear if you are not careful.

JeremyP
The problem with performSelectorOnThread:withObject:waitUntilDone: is that, while waiting, it does not process calls ONTO the current thread. At least in my tests it didn't. i.e. if thread A performSelectorOnThread:B, then B must be able to performSelectorOnThread:A as part of its work.
Chris Becke
But I'll +1 for the elegant use of NSInvocation.
Chris Becke
@Chris Becke: I've added stuff to allow for not waiting. Essentially, if you want to not wait and have a return value, your second thread must call back to the original thread. You can't avoid it.
JeremyP