Hi,
jal wrote:Most (if not all) realtime OSes have synchronous message passing (blocking send; probably because of the realtime constraints?), most other OSes seem to use asynchronous message passing (non-blocking send) or a mix (e.g. MS Windows PostMsg vs. SendMsg). What are the reasons to choose one over the other? In which cases would you choose the one or the other? Are there any performance gains using the one or the other? If anyone could enlighten me I'd be much obliged.
Synchronous messaging is like a function call, except execution is transfered to code in a different task rather than code in the same task. Asynchronous messaging is more like sending data over a network - the sender keeps running.
For synchronous messaging the message data goes directly from sender to receiver. For asynchronous messaging the message data needs to be stored somewhere inbetween (e.g. in the receiver's message queue). This means that (in general) asynchronous messaging has higher overhead.
However, for asynchronous messaging the sender keeps running. This means that for multi-CPU systems one task can send several messages to several other tasks and keep doing useful work (while other CPUs run the other tasks) until it receives the replies from the other tasks - everything can happen in parallel. For synchronous messaging you'd have to do everything in series - send the first message, wait for reply, send the next message, wait for reply, etc, then do the work you could've done while you were waiting. In general, asynchronous messaging is much better for scalability.
Also, asynchronous messaging can reduce the number of task switches. For example (for a single-CPU system), task A can send 100 messages to task B, then you can do a task switch to task B, task B can process the messages and send 100 replies and then you can do a task switch back to task A. In the same situation with synchronous messaging it'll cost you about 200 task switches.
Lastly, usually asynchronous messaging respects task priorities while synchronous messaging doesn't. For most implementations of synchronous messaging, if a high priority task sends a message to a low priority task then there's an immediate task switch (done as part of sending the message) and the low priority task gets CPU time, even if there's medium priority tasks ready to run.
IMHO asynchronous messaging is much much better. However, asynchronous messaging breaks the way people are used to programming. Because synchronous messaging is like a function call it doesn't make much difference to anyone who's used to procedural programming, but asynchronous messaging can completely change the way you do things (for e.g. using an event loop and state machine, rather than doing step A then step B then step C).
For an example, for synchronous messaging you might do something like:
Code: Select all
main() {
int result = 0;
send_FOO();
send_BAR();
do_calculation();
send_FIRST();
result += message.data;
send_SECOND();
result += message.data;
send_THIRD();
result += message.data;
printf("The result was %d\n", result);
}
With asynchronous messaging you might do something like this:
Code: Select all
main() {
char state_flags = 0;
int result = 0;
send_FOO();
do {
message = get_message();
switch(message.ID) {
case FOO_REPLY:
send_BAR();
break;
case BAR_REPLY:
send_FIRST();
send_SECOND();
send_THIRD();
do_calculation();
break;
case FIRST_REPLY:
result += message.data;
state_flags |= 1;
break;
case SECOND_REPLY:
result += message.data;
state_flags |= 2;
break;
case THIRD_REPLY:
result += message.data;
state_flags |= 4;
break;
}
} while (state_flags != 7);
printf("The result was %d\n", result);
}
The asynchronous version looks much more complicated than the synchronous version. However, for the asynchronous version other CPUs can be handling the FIRST, SECOND and THIRD messages while this task is doing "do_calculation()".
To make the sychronous version do the FIRST, SECOND and THIRD messages and "do_calculation()" in parallel, you'd need to do something like:
Code: Select all
volatile result = 0;
main() {
send_FOO();
send_BAR();
threadA = spawn_thread( handle_first );
threadB = spawn_thread( handle_second );
threadC = spawn_thread( handle_third );
do_calculation();
wait_for_thread_exit(threadA);
wait_for_thread_exit(threadB);
wait_for_thread_exit(threadC);
printf("The result was %d\n", result);
}
handle_first() {
send_FIRST();
result += message.data; // Needs to be atomic (use "LOCK ADD")
}
handle_second() {
send_SECOND();
result += message.data; // Needs to be atomic (use "LOCK ADD")
}
handle_third() {
send_THIRD();
result += message.data; // Needs to be atomic (use "LOCK ADD")
}
This is almost as good as the asynchronous version (it does things in parallel), but now we've got the additional overhead of 3 extra threads and need to deal with re-entrancy. In more complicated code the re-entrancy locking, etc can make things much worse than asynchronous messaging would have.
Cheers,
Brendan