📄 threads.doc
字号:
The parameter N is the DOS code defining the cause of the error.
"DOSerror" should return a result of "DOSThread::IGNORE" to ignore
the error, "DOSThread::RETRY" to retry the operation that caused the
error, or "DOSThread::FAIL" to fail the operation. Note that during
critical-error handling, the only DOS services that you can use are
functions 00 to 0C. Class DOSThread will intercept and ignore any
other functions, as they would otherwise cause DOS to crash.
The function "DOSerror" should never be called directly; it will be
called automatically if an error occurs during execution of a thread.
6. Inspecting the status of threads.
------------------------------------
The member function "status" allows you to determine what the
status of a thread is at any time. It can be called as follows:
state = thread1.status ();
The result is a value of type DOSThread::State, which will be one
of the following values:
DOSThread::CREATED -- the thread is newly created and can
be started by calling "run".
DOSThread::READY -- the thread is ready to run (or is
currently running).
DOSThread::DELAYED -- the thread has delayed itself by
calling "delay".
DOSThread::WAITING -- the thread is waiting to enter a
monitor function guarded by "lock".
DOSThread::QUEUED -- the thread is inside a monitor and is
suspended on a monitor queue.
DOSThread::TERMINATED -- the thread has terminated.
7. Using monitors for interthread communication.
----------------------------------------------
One of the problems with multithreaded programs is communicating
between threads. Since you do not know when a thread will be
rescheduled, it is unsafe to modify shared global variables as
it is perfectly possible for you to be interrupted during the
process of updating them. If another thread performs a similar
Page 6
update, you may well complete your update using out-of-date
values when your thread resumes, which means that the global
variables end up in an inconsistent and incorrect state.
The base class DOSMonitor provides a basis for developing classes
which allow safe interthread communication. All you have to do is
to derive a class from DOSMonitor which encapsulates any data which
will be updated by more than one thread and which provides access
functions to access the data. Each access function should begin
by calling the member function "lock" and end by calling "unlock".
This will guarantee that only one thread at a time is executing an
access function in any individual monitor. The general structure
of a monitor access function is therefore as follows:
void MyMonitor::access ( /* parameter list */ )
{
lock ();
... // access shared data as required
unlock ();
}
Classes derived from DOSMonitor can also contain instances of
class DOSMonitorQueue. Within an access function, you can call
the member function "suspend" with a DOSMonitorQueue as its
parameter to suspend the thread executing the access function
until some condition is satisfied. This will allow other
threads to execute access functions within that monitor. The
other access functions can resume any threads suspended on a
particular queue by calling the member function "resume"
with the queue as a parameter. This will reawaken the threads
suspended in that queue.
Note that suspend should be called from within a loop; since
"resume" will restart all the threads in the specified queue,
it is not guaranteed that the condition for which the thread
is waiting will still be true at the time the thread actually
resumes execution. Thus to suspend a thread until a counter
is non-zero, code such as the following should be used:
while (counter != 0)
suspend (some_queue);
As an example, consider a monitor to provide a 20-character
buffer to transfer data from one thread to another. It might
look something like this:
class Buffer : public DOSMonitor
{
char data[20]; // the buffer itself
int count; // no. of chars in buffer
int in; // where to put next char
int out; // where to get next char from
DOSMonitorQueue full;
DOSMonitorQueue empty;
public:
Buffer () { count = in = out = 0; }
void get (char& c); // get a char from the buffer
void put (char& c); // put a char in the buffer
};
Page 7
The class constructor initialises "count" to zero to indicate an
empty buffer and sets "in" and "out" to point to the start of the
buffer. Threads must then call "get" and "put" in order to access
the contents of the buffer. Two DOSMonitorQueue instances are
used; "full" is used to suspend threads which call "put" when the
buffer is full, and "empty" is used to suspend threads which call
"get" when the buffer is empty. The code for "get" would be like
this:
void Buffer::get (char& c)
{
//--- lock the monitor against re-entry
lock ();
//--- suspend until the buffer isn't empty
while (count == 0)
suspend (empty);
//--- get next character from the buffer
c = data [out++];
out %= 20;
//--- resume any threads waiting until buffer isn't full
resume (full);
//--- unlock the monitor to let other threads in
unlock ();
}
9. The class "BoundedBuffer".
-----------------------------
The class "BoundedBuffer" included in this distribution is a template
class derived from DOSMonitor which implements a bounded buffer like
the example above. You can create a 20-character buffer using this
class as follows:
BoundedBuffer<char> buffer(20);
The type given in angle brackets <...> is the type of item that you
want to store in the buffer, and the parameter value is the maximum
number of items the buffer can hold. The following member functions
are provided:
get (item) -- Get the next item from the buffer and store
it in "item". The function returns 1 (TRUE)
if it is successful and 0 (FALSE) if the buffer
has been closed (see below).
put (item) -- Put a copy of "item" into the buffer. This
function returns 1 (TRUE) if it is successful
and 0 (FALSE) if the buffer has been closed
(see below).
items () -- Return the number of items in the buffer.
close () -- Close the buffer to prevent further accesses.
If you do not close buffers when you have
finished using them, you run the risk of your
program never terminating -- a thread may be
suspended waiting for a character that will
never arrive, which means that its destructor
will wait forever for it to terminate.
Page 8
10. Error handling in monitors.
-------------------------------
Monitors derived from class DOSMonitor should provide a virtual
function called "error" which will be called if any errors are
detected in a monitor. "Error" should be declared as follows:
void error (DOSMonitor::ErrorCode);
The parameter to "error" is a code for the error which has been
detected. This can take any of the following values:
DOSMonitor::NEW_FAIL -- there was insufficient memory
to create the necessary data
structures for the monitor.
DOSMonitor::NO_THREAD -- a monitor has been called when
there are no threads running.
DOSMonitor::LOCK_FAIL -- the current thread is calling
"lock" when it has already
locked the monitor.
DOSMonitor::UNLOCK_FAIL -- the current thread has called
"unlock" without having locked
the monitor.
DOSMonitor::SUSPEND_FAIL -- the current thread has called
"suspend" without having locked
the monitor.
DOSMonitor::RESUME_FAIL -- the current thread has called
"resume" without having locked
the monitor.
The last five of these indicate a bug in the monitor code which
should be corrected. The default action if a monitor does not
provide a definition for "error" is to exit the program with an
exit status in the range -1 to -6 (-1 for NEW_FAIL through to -6
for RESUME_FAIL).
11. Potential problem areas.
----------------------------
Class DOSTask uses an internal monitor to guard against re-entrant
calls to DOS, as these are certain to crash your machine. Direct
calls to BIOS functions are not protected in the same way. While
BIOS calls are generally safer (they use the caller's stack), they
still manipulate a global shared data area. It is therefore not
advisable to call BIOS functions directly, as this can lead to
hard-to-identify bugs resulting from an inconsistent internal
state. However, C++ library functions normally use DOS services
rather than calling BIOS functions, so most of the functions in
the standard library are safe to use. The major exceptions to
this are the functions defined in <bios.h> and the functions
"int86" and "int86x".
If you do need to use BIOS functions directly, the best approach
to adopt is to localise all BIOS calls in a single monitor so that
only one task at a time can call a BIOS function; however, since
DOS services will perform their functions by making BIOS calls,
you must also use the monitor to encapsulate all DOS calls to
guarantee that only one task at a time is making a BIOS call.
This may not be a terribly practical solution.
Page 9
Another point worth noting is that screen output is best done
using "fputs" rather than "cout", "printf" or "puts". Each of
these generates several DOS calls to generate their output, and
it is therefore possible for another thread to interleave some
other output with it. In particular, if you use "cout" it is
possible for the same output to appear twice if the thread is
interrupted after the output has been displayed but before the
internal buffer has been cleared. The next thread which uses
"cout" will have its output appended to the existing contents
of the buffer which will then be displayed in its entirety.
A more serious problem (which I have been completely unable to
resolve) is that programs which use direct BIOS calls can crash
the system if high memory is being used. If your program needs
to use direct BIOS calls, you should only do this if you are
NOT using an upper memory manager such as EMM386 or QEMM. There
is obviously some memory management context information which
needs to be saved on a thread context switch, but without any
knowledge of the internal workings of upper memory managers I
do not know how to proceed on this (and if anyone can help me
here, I will be eternally grateful!).
12. A plea for feedback.
------------------------
If you use this class, please contact the author via the addresses
at the beginning; if you don't have e-mail access please send me a
postcard (I like postcards!) just to let me know you've looked at
it. Feel free to suggest enhancements, find bugs or (better still)
fix them and send me patches. Happy hacking!
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -