This site contains older material on Eiffel. For the main Eiffel page, see http://www.eiffel.com.

EiffelThreads: Multithreading for O-O development

Multithreaded applications provide a flexible, exciting way of utilizing the power of modern computer systems. ISE Eiffel supports a powerful multithreaded model, simple and easy to use.

This document describes the EiffelThreads library, the first production-quality implementation of threads for Eiffel. EiffelThreads enables Eiffel developers to take the edge by building impressive multithreaded applications.

EiffelThreads is included in all current versions of EiffelStudio, the Eiffel development environment, for Windows, Unix and Linux.

As explained below, you can in a few minutes download and run the multithreading demo, which will enable you to see the full power of EiffelThreads applied to a lively graphical example.

About the EiffelThreads demo

The demo contains some documentation, the complete Eiffel source for a system utilizing the power of threads, and the executable (fancy_demo.exe) resulting from compiling that system.

The demo runs on Windows 95/98/Me/NT/2000/XP, and makes use of ISE's acclaimed WEL (Windows Eiffel Library) for stunning visual effects. The thread mechanism, however, works on Unix platforms as well as Windows platforms, in a completely portable way.

You can compile the source code of the demo using any version of ISE Eiffel starting with 4.1. But even if you don't have ISE Eiffel, you can still see the demo at work by simply running the delivered executable file fancy_demo.exe.

Downloading the demo

To obtain the demo, download one of the following three files (with the same contents, different formats):

What you will get after unzipping (first version) or uncompressing and untarring (other two) is a directory threads containing documentation, Eiffel classes and the generated executable fancy_demo.exe.

Demo description

To start the demo, just double-click on fancy_demo.exe.

A window comes up, with two subwindows. A different thread runs in each of the three windows: base window, subwindows. The graphical display comes from one of the WEL demos in the delivery of ISE Eiffel 4 for Windows.

You can create as many new windows as you like, from the "Windows" menu. Again, each uses a different thread.

You can resize or move any of the windows. You can also minimize, maximize or kill a window (through the normal Windows buttons at the top right corner). In the last case, this terminates the corresponding thread; the other threads continue undisturbed -- unless of course the window that you are killing is the base window (the one with the two subwindows), corresponding to the parent thread, whose disappearance also destroys all other threads.

Each thread continually creates new objects. Without the garbage collector, the application would bomb pretty soon. If you have access to tools for measuring system activity, such as Microsoft's Pview and the Task Manager under NT/2000, you can check the number of active threads, and the memory usage which is continuously changing (increasing as the threads create objects, decreasing as the GC collects these objects).

We have run the demo on an ordinary Windows 95 laptop for several days in a row, creating millions and millions of objects with no visible degradation in performance.

Differences between Windows NT/2000 and 95/98

The demo runs on both versions of Windows and provides a good illustration of the difference in sophistication between the thread mechanisms of the two platforms. On Windows 95, if you move or resize a window, all the others threads are stopped during the operation. On Windows NT/2000, no interference is visible at all. This points to a much more fine-grain synchronization mechanism. The scheduling policy also seems much more fair: on Windows 95, a thread may go blank for 20 or 30 seconds, temporarily starved, and then restart. This does not happen on NT/2000.

On Windows 95, we have observed the following problem: sometimes, when you start the execution (typically after having started and killed it several times), the original window comes out blank. To get the normal execution going, it suffices to resize the window. This appears to be a problem with the operating system's scheduling mechanism, rather than something in either the demo or the Eiffel mechanism.

The challenge

Apart from the general need to make the runtime and the generated code "thread-safe", i.e. able to have several versions running in parallel reentrantly, the major challenge was to adapt the garbage collector. In particular:

  • An object can appear to be dead to its thread, but still be referenced from objects in other threads. In such a case it must not be collected. If all same-thread and other-thread references disappear, however, the GC must collect the object.
  • As befits an industrial-strength mechanism, the ISE Eiffel GC is a compacting collector: it can move objects around. An elaborate mechanism updates all references to an object that is being moved. But this does not work if the references are from other threads!
  • If there is a single GC, it must stop all threads while it is operating. This would be unacceptable, as a thread which does not do any object allocation (e.g. because it is running an external C routine) would be penalized by greedy threads; also, the GC would need to wait until all threads are ready to stop, so that if a single thread is off doing some non-interruptible task (again, such as executing external code), all others might have to be blocked for seconds, minutes or more.

ISE Eiffel avoids these pitfalls by allocating one GC per thread, and providing simple and effective solutions to any conflicts that could arise between memory management mechanisms for different threads.

The speed of the demo is a clear indication that this technique of one GC per thread is the right approach.

EiffelThreads

Documentation included with the release describes the classes of the EiffelThreads library and their features.

The most important classes are:

  • THREAD: use this class as an ancestor for each class that allows starting new threads. (This is the case with the window classes in the demo.)
  • PROXY: use this class to refer to other threads' objects.

In class THREAD, there is a procedure launch. This procedure calls another, execute, deferred in THREAD. To describe the behavior of a thread on execution, simply effect procedure execute in the appropriate descendant of THREAD. The classes of the demo will provide a good example.

The proxy mechanism

To maintain the safety and consistency of a multithreaded application, it is essential to force a disciplined use, by each thread, of other threads' objects.

The mechanism relies on PROXY objects and is governed by the following rule:

Basic Thread Rule

A thread must never keep references to another thread's objects.

This rule and the notion of proxy were strongly inspired by the separate facility of the general Eiffel concurrency design, SCOOP, scheduled for a future release of ISE Eiffel. (SCOOP is a language extension, whereas EiffelThreads uses a library within sequential Eiffel. SCOOP will support a threaded version, Threads-SCOOP.)

With EiffelThreads, enforcement of the Basic Thread Rule is the responsibility of the programmer.

When a thread needs to access another thread's object, it will use a proxy for that object -- an instance of the EiffelThreads class PROXY[G]. To record the object foreign_object of type FOREIGN_TYPE in a proxy, it will execute

    my_proxy.put (foreign_object)

where my_proxy is of type PROXY [FOREIGN_TYPE]. To access the object, it will then use the expression

    my_proxy.item

This expression should only be used as a target of feature applications, as in

    my_proxy.item.do_something

and never assigned to an attribute (as in my_attrib := my_proxy.item), as this would cause a violation of the Basic Thread Rule, and unpredictable results.

In programming Eiffel thread applications, we have found that the Basic Thread Library Rule is actually of great help, as it forces us -- like SCOOP's separate mechanism -- to define carefully what is shared and what is local to each thread. Of course, access to other threads' objects may also require mutual exclusion and other synchronization mechanisms, which the EiffelThreads classes also provide.

Once routines

Eiffel introduced the powerful mechanism of once routines. A once routine has a body that will be executed only once, for the first call; subsequent calls will have no further effect and, in the case of a function, will return the same result as the first. This provides a simple way of sharing objects in an object-oriented context.

For multithreaded applications, the appropriate semantics is that once routines must be called once per thread (rather than once per process). This is the semantics supported by EiffelThreads.

Release status

EiffelThreads is part of all current Unix, Linux, and Windows deliveries of ISE Eiffel, and is used in a number of developments by ISE customers, both for "traditional" products and for embedded real-time applications.

Other documentation

This document has presented the EiffelThreads library of ISE Eiffel. A complementary effort is the development of SCOOP (Simple Concurrent Object-Oriented Programming), the general ISE Eiffel approach to concurrency and distribution (including multithreading), described in a SCOOP short technology paper.

***

Thread programming through EiffelThreads opens new horizons to software development. We hope you will be as excited about it as we are.