?? threads.doc
字號:
Page 1
Class DOSThread: a base class for multithreaded DOS programs.
---------------------------------------------------------------------
Author: John English (je@unix.brighton.ac.uk)
Department of Computing
University of Brighton
Brighton BN2 4GJ, England.
Copyright (c) J.English 1993.
Permission is granted to use copy and distribute the
information contained in this file provided that this
copyright notice is retained intact and that any software
or other document incorporating this file or parts thereof
makes the source code for the library of which this file
is a part freely available.
1. Introduction.
----------------
Class DOSThread provides a framework for writing DOS applications
which consist of multiple (pseudo-)parallel "threads" of execution.
DOS was not designed as a multithreading operating system (in fact,
it actively hinders multithreading by being non-reentrant) but this
class allows you to create multithreaded DOS applications without
worrying about such problems (although you should read section 11
for some important caveats concerning direct calls to the BIOS).
To create a thread using this class, you must derive a class from
it which contains the code you want to be executed in a member
function called "main". All you have to do then is declare an
instance of your derived class and then call the member function
"run" to start it running. Each thread will be executed in bursts
of 1 clock tick (55ms) at a time before being suspended to allow
the other threads a chance to execute. The length of the timeslice
can be changed if necessary using the member function "timeslice".
If required, timeslicing can be disabled entirely, in which case
it is up to each thread to relinquish the use of the processor at
regular intervals so that the other threads get a chance to run.
Threads can delay themselves for a given number of clock ticks by
using the member function "delay", they can relinquish the use of
the processor to allow other threads to execute by using the member
function "pause", and they can terminate themselves or each other
by using the member function "terminate". It is also possible to
inspect the current state of any thread (ready-to-run, terminated,
delayed and so on) by using the member function "status" and to
wait for a thread to terminate by using the member function "wait".
Two additional classes (DOSMonitor and DOSMonitorQueue) allow you
to derive monitor classes of your own to facilitate communication
between threads. A monitor will normally contain data structures
which can be accessed by several threads. You can guarantee that
only one thread at a time is executing a monitor member function
which accesses the data by calling the member function "lock" at
the start of the monitor function. If any other thread is already
executing a monitor function guarded by a call to "lock", the
current thread will wait until it is safe to proceed. At the end
Page 2
of the monitor function, you should call "unlock" to allow any
waiting threads to proceed. Monitors can also contain instances
of DOSMonitorQueue which allow threads to suspend themselves in a
monitor function until some condition has been fulfilled (e.g.
that a buffer isn't empty). Some other thread executing within
the monitor can resume any suspended threads when the condition
is fulfilled (e.g. after a data item has been put into an empty
buffer). A template class which implements a bounded buffer is
included in this distribution. This is probably the commonest
use of monitors in most applications, so it may well not be
necessary to define any other monitor classes of your own.
If you find this class useful or have any suggestions as to how it
can be enhanced, please contact the author at one of the addresses
given above. E-mail and postcards will both be welcome!
2. Deriving a new thread "MyThread" from class DOSThread.
---------------------------------------------------------
Every thread is created by deriving a new class from the base class
DOSThread. Each derived thread class must provide a definition of
a member function called "main" which contains the code which the
thread will execute. "Main" is declared like this:
void MyThread::main ()
{
// code to be executed by your thread
}
The constructor for your derived class "MyThread" will invoke the
constructor for DOSThread. The constructor for DOSThread requires
a single unsigned integer parameter which specifies the size of the
stack to be allocated for the thread. However, a default of 2048
bytes is assumed, and if this is sufficient you need not explicitly
call the DOSThread constructor at all.
Having created a derived thread class, you can then declare instances
of this class in your program, as for example:
MyThread thread1; // a thread called "thread1"
MyThread threads [5]; // five identical threads
The threads you declare will not be executed until you call the member
function "run", as follows:
thread1.run ();
"Run" returns a result to the calling program which is TRUE (1)
if the thread was started successfully, and FALSE (0) if it could
not be started (either because there was insufficient memory to
create the necessary data structures or because it has already
been started). Note that you cannot call "run" from your thread
constructor since the virtual function "main" is not accessible
until you have finished executing the constructor.
Once a thread has been started successfully, it will be executed
in parallel with the main program. The main program effectively
becomes another thread (although it has no name, and it can only
Page 3
make use of the static functions "pause" and "delay" described
below).
The default is for each thread to be granted a "timeslice" of one
clock tick (55ms). If a thread is still running when its timeslice
expires, it is moved to the back of the queue of ready-to-run threads
and execution of the next thread in the queue is then resumed. The
static member function "timeslice" can be used to change the length
of the timeslices used. "Timeslice" requires an unsigned integer
parameter specifying the desired timeslice length in clock ticks,
as for example:
DOSThread::timeslice (18); // timeslice once a second (18 x 55ms)
If the parameter is zero, timeslicing is disabled. In this case
it is up to individual threads to relinquish control to each other
by calling a member function which will cause another thread to be
scheduled. A member function "pause" is provided for just this
purpose, and is described below.
"Timeslice" must be called before any threads are declared; as soon
as the first thread has been declared, calls to "timeslice" will be
ignored. This means you cannot dynamically change the length of the
timeslice during execution of the program.
3. Writing the member function "main".
--------------------------------------
"MyThread::main" (the main function of your derived class) will be
executed in parallel with the rest of the program once it has been
started by calling "run" as described above. While "MyThread::main"
can be written in exactly the same way as any other function, it is
important to remember that it is sharing the processor with a number
of other threads and that if it has nothing useful to do, it should
allow some other thread to run. The member function "pause" lets you
temporarily release the processor to another thread:
pause (); // schedule another thread
This is a static member function, so is can be called from any
point in a program as "DOSThread::pause". Even if you are using
timeslicing, it is a good idea to call "pause" if your thread is
temporarily unable to proceed (e.g. it is waiting for a key to
be pressed), as otherwise it will do nothing useful for several
milliseconds until its timeslice expires and another thread gets
a chance to run.
You can also make your thread wait for a fixed time by using the
static member function "delay", specifying the delay period as
a number of 55ms clock ticks:
delay (18); // delay for 1 second (18 x 55ms)
Note that "pause" and "delay" are both static member functions
which always affect the current thread. This means that you are
not able to "pause" or "delay" any other thread. It also means
that you can call these functions from the main program if you
need to.
Page 4
When "MyThread::main" returns, the thread terminates. You can also
terminate a thread explicitly using the member function "terminate".
If another thread (or the main program) wants to terminate "thread1",
it can do it like this:
thread1.terminate ();
This is potentially problematical, as you have no idea what "thread1"
is doing at the time. A thread can also terminate itself:
terminate ();
which has the same effect as returning from the main function of
the thread.
4. Initialisation and finalisation.
-----------------------------------
When a thread is declared by the main program or by another thread
the constructor for class DOSThread is called to create the thread
and any constructor defined by your derived thread class is then
called to complete the initialisation. Note that a thread is not
completely constructed until this sequence is complete, and in
particular this means that you cannot call "run" from inside your
derived class constructor to start the thread running immediately.
When you reach the end of a block in which a thread was declared,
the destructor for the thread will be called. Any destructor you
provide in your derived class is called first (while the thread
could still be running), and the standard DOSThread destructor is
then called to wait for the thread to terminate before tidying up.
This means that your destructor should not do anything which might
cause the thread to fail. The member function "wait" allows you to
wait for the thread to terminate, and your destructor should call
this function before doing anything that might cause the thread to
fail. In other words, your destructor should be written like this:
MyThread::~MyThread ()
{
wait (); // wait for thread to terminate
... // do any class-specific tidying up
}
5. Handling "control-break" and critical errors.
------------------------------------------------
Class DOSThread provides a simple mechanism for dealing with events
reported by DOS. The first such event is the "control-break" key
being pressed to abort a program. Class DOSThread intercepts these
events and sets an internal flag. Individual threads (or the main
program) can call the static member function "userbreak" to test
if control-break has been pressed:
if (DOSThread::userbreak ()) ...
The flag will remain set so that other threads can also inspect it.
Alternatively, you can use the static function "cancelbreak", which
is identical to "userbreak" except that it also resets the internal
Page 5
flag. This allows an individual thread to deal with a control-break
event without any other threads being able to deal with the same event
as well as providing a means for resetting the flag. If threads do
not use either of these functions, control-breaks will be ignored
completely.
Critical errors (the familiar "Abort, Retry, Fail?" errors) can be
generated by DOS if a disk is write protected or a printer is offline.
Classes derived from DOSThread can provide a virtual function "error"
to deal with any critical errors they may generate. Threads provide
their own critical error handlers on an individual basis; the default
handler just fails the operation. To provide a critical error handler
for a thread class, define a member function "DOSerror" as follows:
DOSThread::Error DOSerror (int N);
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -