I. A Tutor's Homework
To write multi-threading program could be quite challenging. But to write server program or at least to understand how server programs are working, it is worth spending some effort. We imagine a case supposed to be the simplest. Or say, if I am the tutor of COMP 2xx (probably 3xx, 4xx or something after operating system in most schools), I will use this example. The requirement is as the following:
- start a given number of threads
- each thread execute a radom long task, ranging from ll (lower limit) to ul (upper limit) slices, both inclusive. Each slice of time is atomic, the task can only be terminated between two slices. To simplify this requirement, we just increase an integer and let the thread sleep the same duration as the slice.
- at the beginning of each thread, the task duration set prepresented by the number of slices will be printed to standard output.
- each thread/task may be termininated prematurely, so we use a condition variable 'halted' to control each thread.
- at the end of each task, print the progress of the task, complete or partially completed, shown as the integer number of slices.
This requirement has already been quite technical, like a programmer talking to a programmer, suppose they are nice to each other. In this sense, the simple-ness is compared to the real-life human language. Real-life language can request ``I want to put the customers to three categories: children, woman and man''. But I do believe most programmers would prefer to say ``1. children, male or female under the age 18. 2. male above 18. 3. female above 18.'' An immediate thought jumped to mind is that there will be an exception at the 18th birthday of someone. ``So could you please be more specific?'' A programmer could think quite different even odd. But it is the way to talk to compiler and database system.
Let's come back to our example. After some hours' sourcing, coding, we come to a segment of code. I use Visual C++ 2010 Express. I use Microsoft Windows native threading library, namely Windows.h. I don't have any preference of compilers or operating system. My principle is to solve problems with the minimum cost. That is, the problem should be solved by the mothodology, the tools at hand, not the ideology, or personal beliefs. We discuss the beginning code as the next section.
II. An Early Submission
I thought the requirements are reasonable, implementable, I save the word simple. I review the code several times. I am sure it is going to do what it is supposed to do. You even may say I wrote the requirements after the code is written. So I decide to submit the answer sheet.
III. Something Went Wrong
First, let's see the requirement ``at the beginning of each thread, the task duration set prepresented by the number of slices will be printed to standard output.'' And the result
Is it fine? Check again. It happened for me spending a decent time to format the output to c++ ostream. So at the first glance, I know there is something going wrong with '01: 366'. If you don't specify the padding, alignment etc, it will not happen such '01' occurs. When this code is ported to another machine, the result is even more messy.
My first suspicion is that for this C++ style code, I should not use native Microsoft Windows threading library. HANDLE, CreateThread should be something managed, or should I do it in the `.net' way. The situation is that typically you get those precious helpful example code for some very paticular problems from the open source community, but your application is dependent on Windows platform.
While, it did not take long for me to figure out that std::cout is a shared resource among the threads. It is not a thread-safe ADT instance.
IV. A Quick Fix
It was reported, in a very rare situation, some customer received a bill with an astronaumical figure, maybe positive maybe negative. The DBA swears innocently the database is all-right, the accountant, sales representative ... Is it just because two threads try to access a same stringstream concurrently without synchronization? While this is not something for us to conjecture, or to be concerned. But, we met a similar situation. We know statistics, it is just NOT rare.
We would modify our simple example accordingly.
V. Different Sleeping Values
Our updated version of program is
We set different sleeping value in the main function to observe how the program behave. We know set ll as 1000ms, ul as 3000ms.
First we set Sleep(800).
Second we set Sleep(2000).
Third we set Sleep(5000).
From the above experiments, we know all the child threads exit as the main function terminated. Our current implementation doesn't show each child thread status if we end our program in such a way. So we know why need the condition variables. The usage will be shown in the next section.
VI. Use Condition Variable
We modify the program accordingly.
Again we try to let the threads terminate prematurely by Sleep(800).
Now we can check the child threads' status.
VII. Some Minor Changes
We give some minor changes and the motive(s), and some practice(s) we `believe' as standing foot(feet).
- any primitive/built-in data type variables should be declared as volatile, given any possibility (even the lowest) they will be accessed by more than one thread exists. E.g., bool halted, when the thread where main is in calls (**iter).halt(), it is accessed. In each task thread other than the thread main is being executed, the variable will be accessed once or more again.
- Why not assert? So to speak, why don't we use assert(tmphandle) but use `if (NULL != tmphandle)' e.g.. Because functions like CreateThread etc are system calls that are cancel-point. Namely, at such points, the program may receive a termination signal, the ADT thus created may be NULL in memory space or not completely constructed ( to be NULL is higher in chance). This is run-time error. `assert' will not help. In the program segment that follows, if we access such pointers, the program will cause a segmentation fault thus crashes the service. A service program in a server should be able to tolerate such run-time errors. Like a Web server, the server program should not crash because a single session's run-time error. The service program should have only one end point, during it's life time, exit(int) should never be called. We will discuss the shutdown-hook technique later.
VIII. Some Minor Changes
The source code:
IX. Visaul C++ close handle
Let's experiment with the following program.
X. Visaul C++ close handle
Our experiments will show:
- each time we 'ctrl-c', the program will trigger a new thread (different thread ids) to handle the signal.
- we found to return false or to exit(0) have the same effects from function ConsoleHandler. We should read Visual C++ API, MSDN etc. Such blind tests may be anti-intuitive. In fact, the theoretical explanation is still hinted from UNIX books. It is a weired situation to use Visual C++ with non-managed, non-CLR or C#. But still a lot programmers are there.
is never executed. But we really desire the program comes to this segment, in our experiments, it did not happen. We want to do some our private management, close databases, shutdown the singletons (or pass these references to the closehandle?). Can we do it, we discuss/experiment later?
For the convenience of programming, we continue this topic with Java.
Stylistshop I Requirement
An Exercise of the Synchronization of Autonomous Processes
We use this exercise to analyze a simplified business model. The requirement is as the following. We review threads synchronization in Java at the same time.
the Customer Model
The customer model generates events in this producer-consumer data structure.
the Worker Model
The worker model processes events and report status in this producer-consumer data structure.
synchronized or noise?
Put things in one file.