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
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
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
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
- 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
Just launch the compilation: the ISE compiler will generate and link the multithreaded executable.
Back to index
- 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
- 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
- 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
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
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
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
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
- 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
- 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
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
- 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
- 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
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
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
|