Interactive
Software Engineering
Multi-threading for O-O development

[ISE Home] Home ] Release Notes ] Technology Papers ] Installation Notes ] About Eiffel ]


 1  INTRODUCTION
2  MANIFEST
3  OVERVIEW OF THE CLASSES IN EIFFEL-THREAD
4  COMPILING MULTITHREADED APPLICATION IN EIFFEL
	4.1 SETTINGS
	4.2 COMPILATION
  	4.3 EXTERNAL C FILES

5  HOW TO USE THE EIFFELTHREAD LIBRARY 

	5.1  Creating and launching threads: the CLASS THREAD (deferred)
	5.2  Sharing objects between threads: the class PROXY
	5.3  The class MUTEX
	5.4  The class SEMAPHORE
	5.5  The class CONDITION_VARIABLE
	5.6  The class OBJECT_OWNER
	5.7  Miscellaneous classes
	5.8  Controlling execution: THREAD_CONTROL


 6  ONCE FEATURE IN MULTI-THREADED MODE

	6.1  Manipulating Once features in multithreaded mode
	6.2  Once per Process/Thread
	6.3  Using Once per process/thread features in EIFFEL 
	6.4  Limitations

 FAQ

 

1  INTRODUCTION

Multithreaded applications provide a flexible, exciting way of utilizing the power of modern computer systems. ISE Eiffel supports a powerful multithreaded model, fast and easy to use. The EiffelThread library is mapped to the C thread standard libraries: it supports Windows, POSIX-like and Unix International Threads.

This document describes the EiffelThread library. EiffelThread is supported by the ISE compiler and run-time on Windows, Linux, Solaris, SGI, VxWorks, LynxOS, CRAY and Unixware.

Back to index

2 MANIFEST

The EiffelThread library is located in $EIFFEL5/library/thread. It contains the classes :

- object.owner.e 
- condition_variable.e 
- mutex.e 
- object_control.e 
- thread.e 
- precomp.e 
- proxy.e 
- semaphore.e 
- thread_attributes.e 
- thread_control.e 

Back to index

3 OVERVIEW OF THE CLASSES IN EIFFELTHREAD

THREAD (deferred)

- Description : class defining an Eiffel thread.
- Features :make,  launch, launch_with_attributes, exit.

THREAD_ATTRIBUTES

- Description : class defining thread attributes. Allows the user to set priority, scheduling policy and detach state of the thread upon creation.
- Features : make, set_priority, set_policy, set_detached.

THREAD_CONTROL

- Description : control over thread execution.
- Features :  make, yield, join_all, join, native_join, exit, last_created_thread, get_current_id.

SEMAPHORE

- Description : semaphore synchronization object, allows threads to access global data through critical sections.
- Features : make, post, wait, trywait, is_set, destroy

CONDITION_VARIABLE

- Description : class describing a condition variable.
- Features : wait, signal, broadcast.

MUTEX
- Description : mutex synchronization object, allows threads to access global data through critical sections.
- Features : make, is_set, trylock, has_locked, lock, unlock, destroy.

PROXY

- Description : indirection proxy allowing share of objects between threads, without having the garbage collectors intercollect each-other.
- Features : make, item, is_set.

OBJECT_OWNER

- Description : class meant to record which thread has created a certain object so that another thread can't call its dispose routine.
- Features : record_owner, has_owner, thread_is_owner.

OBJECT_CONTROL

- Description : control over objects meant to be used within different threads.
- Features : freeze, unfreeze.

Back to index

4 COMPILING MULTITHREADED APPLICATION IN EIFFEL

4.1 SETTINGS

- Add the option multithreaded(yes) in your ace file.

- Do not use non-multithreaded precompiled libraries. The corresponding multithreaded libraries of base, WEL and VISION should be located in $EIFFEL5/precomp/spec/$PLATFORM/ with the mt prefix or suffix.

- You must include the thread cluster in your ace file:
thread: "$EIFFEL5/library/thread";

- When using external C libraries, be sure that there are MT-safe: on the platform where the Eiffel Threads are available, the Mel, Wel and Net multithreaded libraries can be recognized with their `mt' prefix.

Back to index

4.2 COMPILATION

Just launch the compilation: the ISE compiler will generate and link the multithreaded executable.

Back to index

4.3 EXTERNAL C FILES

- The C files that you link against a multithreaded Eiffel application must ALSO  BE COMPILED IN MULTI-THREADED MODE. Particularly, you should follow some basic guidelines.

You can find them in any documentation on threads. Here are the main requirements:

- Check what you are importing is safe or not: you cannot arbitrarily enter non-threaded code in a threaded program. Check your include files, and libraries you use for the linking, if they could be used in multithreaded  mode.

- If you are using CECIL in multithreaded mode, you must compile your C files with the same defined symbols as those used to compile the generated C-code in multithreaded mode

- Threaded code can safely refer to unsafe code ONLY from the initial thread.

Note: if you use the libraries Net and MEL in multithreaded mode, you should use libmtnet.a and libmtmel.a. When using MEL, you have to be aware that Motif 1.2 is not threaded-safe (i.e not reentrant). Motif 2.x is threaded-safe.

Back to index

5 HOW TO USE THE EIFFELTHREAD LIBRARY 

5.1 Creating and launching threads: the CLASS THREAD (deferred)

- The class of the thread object you want to create should inherit from the THREAD class.
- Your thread is represented by a class which inherits from THREAD (deferred class).

	class
		MY_THREAD

	inherit
		THREAD
		...

	feature

		execute is
			-- define the deferred feature from THREAD.
		do
			...
		end
		...

	end --class MY_THREAD

- Creating a thread is like creating an Eiffel object:
	my_thread: MY_THREAD
		-- MY_THREAD inherits from THREAD and defines
		-- the deferred procedure `execute'

	create my_thread (or my_thread.make if there is a creation feature).
- Note: - you have created a thread object but not yet launched it.
- to run the thread, use the feature `launch' from THREAD.
	my_thread.launch
- On the Eiffel side, the procedure `execute' will be launched. This procedure is deferred in class THREAD, you have to define it in MY_THREAD. 

- On the C side, a C thread will be created and launched.

IMPORTANT: you may call `join_all' and the end of the exceution of the creator thread if you do not want it to die before its child threads, otherwise they may prematurily terminate.

Back to index

5.2 Sharing objects between threads: the class PROXY

- A recurrent problem while programming multithreaded application is the management of objects shared between several threads: the threads cannot access to these objects at the same time. the synchronization tools CONDITION_VARIABLE, MUTEX, SEMAPHORE are implemented in this purpose. 

- An other problem specific to Eiffel lies on the Eiffel Garbage Collector (GC).  Eiffel has one GC per thread: the Eiffel Threads do not interact during allocation and collection. This implies that an Eiffel object cannot be shared easily between thread, since each Eiffel GC moves and collect asynchronously the objects. To share an object between thread, you must create a proxy object, which encapsulates it. Then, you can share the object by passing the proxy to the other thread and access it with the function `item'.

- To declare a proxy of object SHARED_TYPE:

	my_proxy: PROXY [SHARED_TYPE]
- To create it: (be sure your object `my_item' is already created. )
	create my_proxy.put (my_item)
- During the execution of your thread (take it literally: it means when the procedure execute is running), you will access the shared object throughout the proxy:
	`my_proxy.item' corresponds to the encapsulated shared object.
NOTE:

When a THREAD object is launched, it is duplicated. The new thread uses this copy as the root object for  its execution. The THREAD object in the father thread and in the child thread are two different objects. 

- What kind of object can we put into a PROXY?

For the moment only flat Eiffel objects, i.e any Eiffel objects which do not contain any sub-references. You cannot put any expanded object. If you want to share an object of basic type, use INTEGER_REF, BOOLEAN_REF, POINTER_REF, CHARACTER_REF, BIT_REF, DOUBLE_REF, or REAL_REF instead.

There is no need to use a proxy to share an instance of MUTEX, CONDITION_VARIABLE or SEMAPHORE.

Back to index

5.3 The class MUTEX

The implementation of the class MUTEX is mapped on the C standard thread library. An instance of class MUTEX can be shared between different thread without putting it into a PROXY object.

- my_mutex.pointer is the pointer to the nested C mutex of my_mutex.

- Declaration of the mutex :

	my_mutex: MUTEX
- Creation of mutex :
	create my_mutex.make
- Locking  the mutex : 
	my_mutex.lock
- Unlocking the mutex :
	my_mutex.unlock
- try_lock: if it is not locked yet, lock the mutex and return True, otherwise it returns False.
	my_mutex.try_lock
- Is my mutex initialized?
	my_mutex.is_set
NOTE: on Windows:

The MUTEX objects on Windows are recursive while they are not on Unix. A recursive mutex can be locked twice by the same thread. 

IMPORTANT: be sure that a mutex is unlocked when it is disposed.

Back to index

5.4 The class SEMAPHORE

Like MUTEX, the features of this class are mapped on the C thread library. An instance of class SEMAPHORE can be shared between thread without putting into a PROXY object.

- Declaration of the semaphore :

	my_sem: SEMAPHORE
- Creation of semaphore: initialize semaphore with nb_tokens, it requires nb_tokens >= 0
	create my_sem.make (nb_tokens) 

- Wait for a token:

	my_sem.wait
- Give back a token:
	my_sem.post
- try_wait, similar to try_lock from MUTEX, if a token  is available, take it and return True, otherwise  return False.
	my_sem.try_wait

IMPORTANT: be sure that a semaphore does not wait for a token when it is disposed

Back to index

5.5 The class CONDITION_VARIABLE

This class allows to use condition variables in Eiffel. An instance of class CONDITION_VARIABLE can be shared between threads without putting into a PROXY object.

- Declaration of the condition variable

	my_cond: CONDITION_VARIABLE
- Creation:
	create my_cond.make
- Wait for a signal (send by `signal'). You need to use a mutex. 
	my_mutex: MUTEX

	create my_mutex.make

`my_mutex' must be locked by the calling thread so as `wait' can be called. `wait'  atomically unlocks `my_mutex' and waits for the condition variable `my_cond' to receive a signal. As soon as it received a signal, `my_cond' locks `my_mutex;

	my_mutex.lock
		-- You must lock `my_mutex' before calling `wait'.

	my_cond.wait (my_mutex)
		-- Here the critical code to execute when `my_cond' received a signal.

	my_mutex.unlock
		-- Unlock the mutex at the end of the critical section.

	
 Send a signal one thread blocked on the condition variable `my_cond'.
	my_cond.signal
- Send a signal to all the threads blocked on the condition variable `my_cond'.
	my_cond.broadcast

IMPORTANT: be sure that a condition variable is unblocked when it is disposed.

Back to index

5.6 The class OBJECT_OWNER

Used by MUTEX, SEMAPHORE, PROXY, and CONDITION_VARIABLE, this class prevents an object from being disposed by a thread, which is not its creator thread.

- You do not need to know how to use it.

- Only the thread which has created an instance of OBJECT_OWNER can dispose it.

Back to index

5.7 Miscellaneous classes

- class THREAD_ATTRIBUTES : defines the attributes of an Eiffel Thread.
- class OBJECT_CONTROL : control over objects meant to be used within different threads. To use with care: it can allow to share non falt objects between thread. 
(refer to the implementation and comments in the code of these classes)

Back to index

5.8 Controlling execution: THREAD_CONTROL

- yield: the calling thread yields its execution in favor of an other thread of same priority.

- join_all: the calling thread waits for all other threads to finished (all its children).

-> A parent thread can wait for the termination of a child process through the feature `join' of class THREAD_CONTROL (inherited by THREAD):

	thr: MY_THREAD
	...
	thr.launch
	...
	thr.join

Back to index

6 ONCE FEATURE IN MULTI-THREADED MODE

6.1 Manipulating Once features in multithreaded mode

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 EiffelThread. 

Then the once feature is not initialized once per process but once per thread. Your once feature will be recalled in any new thread execution. 

Back to index

6.2 Once per Process/Thread

- Current once features in Eiffel are once per thread. This means that when a once feature is called in a thread, Eiffel run-time will check whether it has been already computed IN this thread. If not, the once feature will be initialized and computed. This seems to be a relevant way of managing once features in multithreaded mode: most of time, a once called in a thread is not likely to share its result. 

- Besides, for reasons we do not want to detail here, current implementation of threads with a non once per thread implementation would require to encapsulate each once feature, which could be called by several threads, in a proxy. 

- That would mean heavy signatures in thread creation procedures and inelegant lines of code. However, in some case, we need to share once features.

- Moreover, an Eiffel programmer should be able to have an alternative between a once per thread or per process implementation. 

Back to index

6.3 Using Once per process/thread features in EIFFEL

- Keeping the default management of the once features in EIFFEL, a once per process could be implemented by calling the external C function 'eif_global_function' as described below:

	class
		TEST_ONCE_PER_PROCESS

	inherit
		ONCE_CONTROL -- contains global_once_function
		...
	feature
	
		object_per_thread: OBJECT is
				-- once per thread
			once
				create Result.make
			end

		object_per_process: OBJECT is
				-- new 'object' (once per process)
				-- that could be shared between threads
				-- without reinitializing it
			once
				Result ?= global_once_function (Current, $object_per_thread)
			end
			
		...

	end -- class TEST_ONCE_PER_PROCESS

	class
		ONCE_CONTROL
		...
	feature

		global_once_function (Currt: ANY; once_func: POINTER): ANY is
				-- returns the result of the once feature
				-- pointed by 'once_func' which will be 
				-- evaluated once per process
			external
				"C | %"eif_once.h%""
			alias
				"eif_global_function"
			end
		...

	end -- class ONCE_CONTROL 

- Note that object_per_thread is passed through a pointer so as not to be computed once more, at the first call in a new thread.

- We admit that passing 'Current' as a formal argument of global_once is not very elegant, but this is necessary to evaluate 'object_per_thread'.

- In the same way, there is a once_per_process procedure mechanism:

 	procedure_per_process is 
		once
 			global_once_procedure (Current, $procedure_per_thread)
 		end

 	procedure_per_thread is
 		once
 			-- do something --
 		end
in class ONCE_CONTROL:
 	...
	global_once_procedure (Currt: ANY; once_proc: POINTER) is
 		external
 			"C | %"eif_once.h%""
 		alias
 			"eif_global_procedure" 
 		end

 	...

Back to index

6.4 Limitations

At the moment, you must call the once_per_process for the first time in the process in the root_thread. If you did it in a thread, it will work until this thread dies (because it will reclaim the once_per_process object that belongs to its private memory). 

Such as the proxy objects, your once per process function must return a flat Eiffel object (no subreferences in it).

Using interwoven once per process functions could cause a deadlock (the once per process table is protected by a mutex: if you call a once per process in another once per process for the first time in a process or a thread, this will raise a deadlock. This can happen in some tricky situation.

Ex: Do not write:

	my_opp1: A_TYPE is
		once
			Result ?= global_function (Current, $my_opt)  
			-- lock access to 
			-- the once per process functions table
		end

	my oppt: A_TYPE is
		once 
			create Result.make (my_opp2)  
		end


	my_opp2: A_TYPE2 is
		once
			global_once (Current, $my_opt2) 
			-- will fail in attempting to lock the once
			-- per process functions table
		end

	my_opt2: A_TYPE2 is
		once
			create Result.make
		end 

note: Nevertheless, you can call on once per process procedure in a once per process function  and vice versa since  they do not lock the same once per process table.

Back to index

FAQ

I've launched several threads and they do not seem to be executed:

The thread that has launched the several threads may be dead before its child threads.

Back to index

Two threads can lock the same mutex, however none of them unlock it:

Same problem as above. May be the first thread that locked the shared mutex died before the second has tried to lock it: then, the 1rst one has automatically unlock it when dying. You should put a join_all or a infinite loop in the father thread.

 

I've added the option multithreaded (yes) in my ace file and it crashes:

If you have already compiled your system in non-MT mode, you cannot change the mode of compilation in your ace file and simply relaunch your compilation (the generated C-code would be incompatible). Delete all your object files in your W_code or F_code directory and freeze or finalize the system again.

Back to index

My once function changed during my MT-Eiffel-program:

The once functions are once per thread in Multithreaded mode. Hence, each once function is thread specific and is initialized the first time it is called in a thread.

You can create a once per process functions by two ways:

  • Using the external functions provided in ONCE_CONTROL. (MT-safe)
  • Putting the once function in a proxy, the threads other than its creator will have to access to it through `item'. (not MT-safe: you should call the once in mutual exclusion).

Back to index