Chapter 2. Creating a thread in Delphi.
In this chapter:
- A diagrammatic interlude.
- Our first non-VCL thread.
- What exactly does this program do?
- Issues, problems and surprises.
- Start-up issues.
- Communication issues.
- Termination issues.
The application has one thread of execution: the main VCL thread. The progress of this thread can be illustrated with a diagram showing the status of the thread in the application over time. The progress of the thread is represented by a line, and time flows down the page. I have included a key in this diagram, which applies to all subsequent thread diagrams.
Note that this diagram does not indicate much about the algorithms executing. Instead it illustrates the ordering of events over time, and the state of the threads in the application between those times. The actual distance between different points in the diagram is not particularly important, but the vertical ordering of those points is. There are several pieces of information to be gleaned from this diagram.
- The thread in this application is not continually executing. There may be long periods of time where it receives no external stimulus, and is not carrying out any calculations or operations at all. The memory and resources occupied by the application exist, and the window is still on the screen, but none of its code is executing on the CPU.
- The application is started, and the main thread executes. Once it has created the main window, it has no more work to do, and it drops into a piece of code in the VCL known as the application message loop that queries the operating system for more messages. If there are no more messages to be processed, the operating system suspends the thread, and the thread is now suspended.
- At some later point, the user clicks on the button to display the text message. The operating system wakes up (or resumes) the main thread, and gives it a message indicating that a button has been clicked. The main thread is now active again.
- This resume - suspend process occurs again several times. I have illustrated a wait for user confirmation to close the message box, and a wait for the close button to be clicked. In practice, many other messages might be received.
This particular example consists of a program which calculates whether a particular number is prime or not. It contains two units, one with a conventional form, and one with a thread object. It more or less works, but has some decidedly unpleasant quirks, which illustrate some of the basic problems which multithreaded programmers face. We will discuss ways of circumventing these problems later on.
What exactly does this program do? Each time the "Spawn" button is clicked, the program creates a new thread object, initializes some fields in the object, and then sets the thread on its way. Depending on the size of the input number, the thread grinds away calculating whether the number is prime, and once it has finished the calculation, the thread displays a message box, indicating whether the number is prime. These threads are concurrent, whether you have a uniprocessor or multiprocessor machine; from the point of view of the user, they execute simultaneously. In addition, this program does not limit the number of threads created. As a result, you can demonstrate that there is true concurrency in the following manner:
- Since I have commented out an exit statement in the prime number determination routine, the time taken for the thread to run is roughly proportional to the size of its input. I have found that with an argument of about 2^24, the thread takes about 10-20 seconds to complete. Find a value that produces a similar delay for your machine.
- Run the program, enter in your large number, and click the button.
- Immediately enter in a small number (say 42) and click the button again. You will notice that the result for the small number is produced before the result for the large number, even though we started the large number first. The diagram below illustrates the situation.
In the case of this program, the "FreeOnTerminate" and "TestNumber" properties of the thread are set before the thread starts executing. If this was not the case, then the behaviour of the thread would be undefined. If you don't want to create threads suspended, then you simply move the start-up problems into the next category: communication issues.
- Accessing any form of shared resource between two threads.
- Playing with thread unsafe parts of the VCL in a non-VCL thread.
- Attempting to do graphics operations in a separate thread.
The good news? You can do all three of the above if you use the correct mechanisms for controlling concurrency, and it's not even that hard! We will be looking at a simple way of solving communication issues via the VCL in the next chapter, and more elegant (but complicated) methods later on.
The first is to let the thread handle the problem itself. This is mainly used for threads that either a) Communicate the results of thread execution back to the main VCL thread before termination. or b) Do not contain any information useful to other threads at termination time. In these cases, the programmer can set the "FreeOnTerminate" flag in the thread object, and it will dispose of itself when it has finished.
The second is to have the main VCL thread read data from the worker thread object when it has finished, and then dispose of the worker thread. This is covered later in Chapter 4.
I have side-stepped the issue of communicating results back to the main thread by having the child thread present the answer to the user via a call to "ShowMessage". This does not involve communication with the main VCL thread, and the ShowMessage call happens to be thread safe (by and large), so the VCL stays happy. As a result of this, I can use the first approach to Thread deallocation, and let the thread dispose of itself. Despite this, the example program illustrates one nasty feature of having threads clean up after themselves: