Synchronization Mechanisms and Yahoo! (contd...)
Hey, we are back to Yahoo! Messenger stuff. My earlier entry contained details about a Yahoo! Messenger client for Linux. That was done way back in 2001-2002 when I didn't have much programming experience (I mostly wrote console applications and that too were mainly mathematical or network oriented).
Now, after some years of experience working under Windows/WinCE, I look back on the Yahoo! client and find some problems.
One of the problems was that using 3 timers was a bad idea. The functions were called at regular intervals of some millisecs. The time between 2 calls was wasted. So the network response would be slow. Ideally they should have been put in a while loop, and this loop should have been in a separate thread. I still don't know about threads in Linux, but that should not deter me from my discussion here. Using threads simplifies one more thing. All our networking code was non-blocking (the sockets were opened in non-blocking mode). This wastes unnecessary CPU cycles. Putting those functions in threads could have allowed the networking code to blocking in nature.
Another problem was that although, we wrote code in C++, our design of classes was arbitrary. Luckily we had some sense to keep networking code separate from the GUI code. That's why we made queues. These queues were the link between networking and GUI code. Let's not go into issues of object-oriented design in C++ here (that might be done in some other post).
Let's now see the design of our client from the perspective of threads. Assuming that those 3 functions are in separate threads, we have another problem. These functions access same queue for reading and writing and without any synchro mechanisms, the data in queue will be corrupted. We could use any of the mechanisms mentioned in the previous blog entry to solve this problem. But the approach of using Message Queues seems to be the best one, and I discuss it next. Ah! finally, the synchro stuff is linked with Yahoo!
So we replace the Send and Receive queues with OS provided message queues. Note that reads and writes to a message queue are handled atomically so that the reads and writes do not interfere. The operating system takes care of the synchronization issues. Now the function SendToNetwork() would simply read from Send queue and deliver it to network. Reads from a message queue are blocking in nature, so that does not waste CPU cycles. Only when there is data in the send queue, the SendToNetwork() function will consume CPU (compare with previous design: the function was called whether there was data available or not). Similar is the case with function GetMessage().
By the way did you notice, that we could combine the two functions ReceiveFromNetwork() and GetMessage() into one function which would get data from network and deliver it to the GUI, without putting it in a Receive queue. We had kept a queue only to separate network/GUI code. Hey, but you can not remove the Send queue in the same way. It is required because we don't want user interaction thread to wait for the messages to be sent to network.
So you see how useful message queues are when used properly. Compared to other mechanisms like events and semaphores they are costly (message queues need memory for messages), but when you need to pass data along with the synchronization signal, you need to message queues in some form or the other.
At last, let me come to whole point of this entry. Understanding these synchronization mechanisms is not possible without using some multi-threaded application. Reading an OS book won't help unless you apply the ideas in real world. The worst part of technical books is the jargons like Producers and Cosumers which complicate things. So, instead of telling students to write crafted applications (these apps are compelled to use pipes/message queues/events just for the sake of using them), professors should concentrate on genuine ones.
Now, after some years of experience working under Windows/WinCE, I look back on the Yahoo! client and find some problems.
One of the problems was that using 3 timers was a bad idea. The functions were called at regular intervals of some millisecs. The time between 2 calls was wasted. So the network response would be slow. Ideally they should have been put in a while loop, and this loop should have been in a separate thread. I still don't know about threads in Linux, but that should not deter me from my discussion here. Using threads simplifies one more thing. All our networking code was non-blocking (the sockets were opened in non-blocking mode). This wastes unnecessary CPU cycles. Putting those functions in threads could have allowed the networking code to blocking in nature.
Another problem was that although, we wrote code in C++, our design of classes was arbitrary. Luckily we had some sense to keep networking code separate from the GUI code. That's why we made queues. These queues were the link between networking and GUI code. Let's not go into issues of object-oriented design in C++ here (that might be done in some other post).
Let's now see the design of our client from the perspective of threads. Assuming that those 3 functions are in separate threads, we have another problem. These functions access same queue for reading and writing and without any synchro mechanisms, the data in queue will be corrupted. We could use any of the mechanisms mentioned in the previous blog entry to solve this problem. But the approach of using Message Queues seems to be the best one, and I discuss it next. Ah! finally, the synchro stuff is linked with Yahoo!
So we replace the Send and Receive queues with OS provided message queues. Note that reads and writes to a message queue are handled atomically so that the reads and writes do not interfere. The operating system takes care of the synchronization issues. Now the function SendToNetwork() would simply read from Send queue and deliver it to network. Reads from a message queue are blocking in nature, so that does not waste CPU cycles. Only when there is data in the send queue, the SendToNetwork() function will consume CPU (compare with previous design: the function was called whether there was data available or not). Similar is the case with function GetMessage().
By the way did you notice, that we could combine the two functions ReceiveFromNetwork() and GetMessage() into one function which would get data from network and deliver it to the GUI, without putting it in a Receive queue. We had kept a queue only to separate network/GUI code. Hey, but you can not remove the Send queue in the same way. It is required because we don't want user interaction thread to wait for the messages to be sent to network.
So you see how useful message queues are when used properly. Compared to other mechanisms like events and semaphores they are costly (message queues need memory for messages), but when you need to pass data along with the synchronization signal, you need to message queues in some form or the other.
At last, let me come to whole point of this entry. Understanding these synchronization mechanisms is not possible without using some multi-threaded application. Reading an OS book won't help unless you apply the ideas in real world. The worst part of technical books is the jargons like Producers and Cosumers which complicate things. So, instead of telling students to write crafted applications (these apps are compelled to use pipes/message queues/events just for the sake of using them), professors should concentrate on genuine ones.
2 Comments:
yaar apana koi photo shoto bhi lagaa do blog pe...
so a nice piece.
but you cannot merge ReceiveFromNetwork() and GetMessage() for two reasons:
1) the data from the n/w could be faster than you handling them. So you may need a queue and then handle one by one later.
2) When multiple receivers or multiple logins running. Then you need a dispatcher or something like that. This dispatcher come in between ReceiveFromNetwork() and GetMessage() which reroutes the packet to corresponding application using userid or something like that.
Post a Comment
<< Home