next up previous
Next: Running the GTK main Up: Adding a Graphical User Previous: GTK main loop vs

Subsections

Threading

Threading is essentially doing two or more things at once. So far the programs that we have written have been sequential, there is one flow of control that executes each statement in order. In threading, there can be multiple flows of control.

A thread is a single sequential flow of control that cannot exist by itself. It exists within a program and so shares memory with that program.

Single thread in a program

In our MD program, if we wanted to run the simulation at the same time as our GTK main loop, we would put it in a thread. We could also put the minimiser in a thread, and use them alternately, or even at the same time.

Two threads in a program

The result, as shown above, is two or more flows of execution running at the same time. Of course, they are not actually running simultaneously. What is really happening is that the python interpreter is switching between the two threads, giving them equal amounts of time to execute some code, and then switch again. This switching is done hundreds of times a second, so to us, they seem to be running simultaneously.

Not only do the two threads run at the same time, they also share the same memory. For example, in our MD program, we might have a GTK controller in the main program thread, and a simulator in another thread. They would both share a visual renderer, so the simulator would be able to call update, and the controller would be able to call another method, say a method that changes the refresh rate, on the same object. Not only that, but they can call methods on each other. While the simulator is running, the controller can call a method that will tell it to stop.

One big issue that arises in multi threaded applications is synchronisation. Let's say that we have our GTK controller running, and our simulator waiting for someone to click start. How does the simulator wait for this event? A simple approach would use the following code:

def start(self):
    self.simulate = True

. . .

while self.simulate == False:
    pass

In this code, the program would continually check to see if the simulate variable was False. As soon as another thread called start, the program would continue. This is a bad implementation. The problem is that it uses busy waiting. Busy waiting means that the program is continually doing something while it's waiting, such as our example above where it is continually checking to see if it can go. This is extremely inefficient, as the thread doesn't need to be doing anything at all.

Fortunately, Python provides many methods of synchronisation. We will only look at one. The simplest method that Python provides is through the use of events. An event is a flag that is either true or false. It is created by calling threading.Event(). It can be set to true using set() and cleared to false using clear(). It also has a isSet() method that returns true if the event is set, and false if the event is not set. So far, this is no more than a boolean flag. There is one more method that event has, the wait() method. The wait() method waits until the event is set, and then returns. It does this without the use of busy waiting, and so overcomes the issues with our previous implementation.

Implementing threads in Python

All Python threads inherit from the threading.Thread class. The thread is set up by the __init__() method in the threading class, this means that if the __init__() method is redefined, the __init__() method of the thread class must be explicitly called.

Once set up, a thread is started by calling the start() method on that class. The start() method must not be redefined. It starts the new flow of execution, once it has done that, it calls the run() method in the class. This is a deferred method, and so must be implemented. This is where we will implement the simulation.


next up previous
Next: Running the GTK main Up: Adding a Graphical User Previous: GTK main loop vs
James Roper 2004-02-12