On Jan 26, 2009, at 7:55 AM, John Love wrote:

Reference: "Configuring a Port-Based Input Source" of
http://developer.apple.com/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/chapter_6_section_5.html#/ /apple_ref/doc/uid/10000057i-CH16-SW7

My challenge is to try to understand this part of Chapter 5 of Apple's "multithreading.pdf". My single question is "Is my understanding correct?" .. and the best way I can approach this is to go almost line-by-line of the code presented.

This is a fairly advanced topic. Why are you delving into it? What do you need to accomplish?

If you can target Leopard and later, and if you're communicating between threads of a single process, then you can probably achieve everything you need much, much more simply using the various performSelector... methods.


First and foremost, I understand that a NSMachPort is a two way port: for the main thread, the "remote" port is the background port and for the background Thread, the "remote" port is the main port. In short, the same port is used for two-way communication.

Incorrect. In this message and your Part 2, you seem to ascribe special meaning to terms which are only relevant in the context of Apple's example. I think it's confusing you.

First, ports are one-way communication channels. Only one thread should be receiving messages on a given port, although messages to that port may be sent from multiple threads. To achieve two-way communication, you use two ports. The page you reference has this to say:

"In order to set up a two-way communications channel between your threads, you might want to have the worker thread send its own local port to your main thread in a check-in message. Receiving the check-in message lets your main thread know that all went well in launching the second thread and also gives you a way to send further messages to that thread."

and this:

"When using NSMachPort, local and remote threads can use the same port object for one-way communication between the threads. In other words, the local port object created by one thread becomes the remote port object for the other thread."

Note that there is no real distinction at the implementation level between "local" and "remote" or "background" and "main" ports. A port is just a port. Apple uses the terms "local" and "remote" in an attempt to clarify which thread receives messages from a port, and those terms are dependent on "point of view". If two threads, A and B, are communicating using ports, then from the point of view of thread A, the local port is the port which has been added as a source to its run loop and is therefore the port on which it receives messages. Still from the POV of thread A, the remote port is a way for it to send messages to "somewhere else" -- that somewhere else being, figuratively, a remote place. Thread A does not receive messages on that port.

Now, obviously, from the point of view of thread B, the two ports have the opposite roles. When thread B wants to send a message to thread A, which it considers to be a remote place, it sends them on the "remote" port. Thread B has added a separate port as a source for its run loop, and thus receives messages on that port. It considers this port "local".

Now, I'm not totally sure that Apple's terminology is all that helpful, but there you go. It's hard to come up with good terminology for this subject matter.

As for the terms "background port" and "main port", I'm really not sure what you're getting at. I don't think those terms are especially meaningful.


Within Apple's –launchThread, they have:

NSPort* myPort = [NSMachPort port];

This appears to be a new background Port. Is it, or is it a new local main Port? I think it is a background Port because Apple continues by calling – detachNewThreadSelector with an object equal to this Port.

Again, you're confusing yourself. It's just a new port. The _role_ that this code then uses it for is to receive messages in the current thread, which happens to be the main thread. Using Apple's terminology, from the point of view of the main thread, it's "local". From any other thread, it would be a "remote" port.


Within the selector passed to –detachNewThreadSelecctor:

+(void)LaunchThreadWithPort:(id)inData
(actually, I convert it to an instance method - don't know why Apple uses a class method here
?)

It's relatively common practice. The method doesn't need any state information. It doesn't need to be an instance method, so it isn't. Also, when doing multithreading, it's best to avoid sharing data between threads to the greatest extent possible. If it were an instance method, then there would by necessity be an object which is shared between the main thread and thread being detached. That sharing might be fairly trivial, but if you can avoid it easily, you might as well.

Since the instance of MyWorkerClass only exists locally within the +LaunchThreadWithPort: class method, there's no chance that you might inadvertently directly message it from the main thread. That's a good defensive coding practice.


the passed inData is immediately converted:

NSPort* distantPort = (NSPort*)inData;

Where their distantPort, or remote Port, is the remote Port for the main Thread, thus making the passed Port the background Port.

Again, more confusing terminology that seems to be spinning your head around. I know the above sentence spun mine around.

The main thread created a port on which it will receive messages. In order for it to receive messages, there have to be senders. How are they going to send messages to the main thread unless they have a reference to the port? So, the main thread passes such a reference to them.

That's all that's going on here. The secondary worker thread has been detached and it has been passed a reference to a port over which it can send messages to the main thread. From the point of view of the secondary thread, this port is "remote" because it gets messages to a (figuratively) remote destination. This self-same port is seen differently by the main thread -- it's seen as "local" because it gets messages to the local destination, the main thread itself. But don't confuse these perceptions as an inherent characteristic of the port.


Here is where my mainCtrl parm comes into play, because instead of Apple's:

Within my

do
{

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

} while (![mainCtrl shouldExit]);

where my – (BOOL) shouldExit is in my main Thread.

Huh? What does it that -shouldExit is "in" your main thread? It's being invoked right here in the code which will be running in your secondary worker thread.

Apple's code created an object, an instance of MyWorkerClass. This object is intended to keep the state for some work task that needs to be done. The code creates a port, assigns the worker object as the delegate (message handler) of that port, and schedules that port on the run loop. This is a way for other threads to send messages to the worker object for it to handle. The reason why -shouldExit is sent to the worker object is that it's assumed that only the worker object will have the necessary internal state information to know when it's time to exit.


Am I still on the right track? I definitely am off-base somewhere because there is no evidence that mainCtrl's –shouldExit is even called?

The call to -runMode:beforeDate: blocks until an input source fires. The loop around it keeps calling it until the worker object decides that it should exit. However, the -shouldExit method won't be invoked spontaneously. It will only be invoked after runMode:beforeDate: exits after having processed the firing of an input source.

In Apple's design, this is fine. The only thing which should change the state of the worker object in such a way that -shouldExit might now return TRUE is a message received on the port -- which is an example of a run loop source firing. In other words, -shouldExit is checked immediately after the sort of event which might have caused it to become TRUE. Absent such an event, the thread is blocked in the run loop and isn't checking -shouldExit.

In your design, you seem to think that changing the internal state of some other object, mainCtrl, whatever that is, will be good enough. But that's not so. The internal state of the mainCtrl object, even if it would result in -shouldExit returning TRUE, doesn't matter because the worker thread isn't checking -shouldExit. It won't check that until one of its input sources fires. (The run loop may have other input sources than the port that your code explicitly adds to it.)


Further, I just use:    [self sendCheckInMessage:distantPort];

Well, you've already said that you are using an instance method as the thread's main method, so that's to be expected, I guess.


and within Apple's – (void) sendCheckinMessage :(NSPort*)bgndPort, they have:

NSPort* myPort = [NSMachPort port];

Apple states: "Create and configure the worker thread port". Is myPort a local main Port for the worker thread port to send its message back to?

Once again, this is just a port. It's going to be used to receive messages in the secondary worker thread. As such, it's local from the point of view of the worker thread; it's remote from the point of view of any other thread. It's not a "main" port, whatever you think that means.

And if so, why is this required because doesn't Cocoa know about the main port that it presumably created when it called:

NSApplicationMain(argc, (const char **)argv);

at the very start within "main.m"?

Whoa! Huh? Where'd you get an idea like that? I don't know what you're talking about here.

There is no such thing as a main port. There isn't any "special" port that has a predetermined purpose. I'm sure there are ports used under the hood throughout the framework, but that would be totally private, internal implementation detail. There's certainly no documented creation of a port in NSApplicationMain.

Even if there were, the port being created here is for a custom purpose -- the receipt of messages on the worker thread -- so it could have no shared purpose with any predefined port you might have imagined.


One last basic question -- where exactly should my – doSomeSuperLongCalculation method go? Should it be sandwiched somehow within –shouldExit,? Is it inserted within the main thread's –handlePortMessage, or is it placed within the above do- while loop?

The worker object has created a port on which it is to receive messages, it has scheduled that port on its run loop, it is running its run loop. When something sends a message to that port, the run loop of the worker thread wakes up and sends -handlePortMessage: to the port's delegate. The worker object has configured the port to use the worker object itself as the delegate. So, the worker object class should have a -handlePortMessage: method implementation. That isn't shown in Apple's code. The -handlePortMessage: implementation that they do show is the main thread's, which is separate.

So, you'll need to supply a -handlePortMessage: method. You will also have to define a communication protocol describing the messages that the worker knows how to handle, so the senders know what to send and the worker knows how to interpret what they're asking.

One kind of message may be a request to a do a super-long calculation, in which case -handlePortMessage: might invoke - doSomeSuperLongCalculation on "self", which is the worker object.

How does the main thread know how to send requests to the worker thread? It's because the worker thread passed a reference to the port on which it is receiving messages to the main thread via the check-in message. The port is passed in a somewhat indirect manner. The check- in message, like all NSPortMessages, carries with it the notion of a reply port. In this case, the code has been written so that the reply port is the general port for the worker thread.


I hope that clarifies this subject. As I say, you are likely to be much better off using the new features of Leopard and avoiding all of this custom run loop source stuff entirely.

Cheers,
Ken

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to