Re: Configuring a Port-based Input Source -- Part 1
Re: Configuring a Port-based Input Source -- Part 1
- Subject: Re: Configuring a Port-based Input Source -- Part 1
- From: Ken Thomases <email@hidden>
- Date: Wed, 28 Jan 2009 00:55:52 -0600
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 (email@hidden)
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:
This email sent to email@hidden