class ref name: thread
category-group: os
layer: 1
header file: z_thread.h
libraries: libz00.lib libz01.lib

synopsis.
the thread_o class is the core class for creating a multi-threaded application in the Z Directory. This class has been made to be as simple as possible, yet retains the full functionality of threads. The concept of threaded programming is not explained on this page - it is assumed you know about the basics of what threads are about. Each thread instance contains its own semaphore. The application must subclass from the thread_o class. This subclass must implement the process() [pure virtual] member function, which acts as a "callback" - it is called when the thread is executed. Two virtual functions, config() and cleanup() are provided to do a thread's pre- and post- run processing.
There is a specialized thread class, unithreaad_o , which provides a framework for a single thread to do background work. This class is particularly well-suited for GUI programs that use a continuous event loop, such as Qt or MFC.
There are some specifics one needs to know about threads, at least in this implementation in order to use it. See the main discussion (below).

description.
some notes.
When thread_o::run() is called, thread_o::reset() and thread_o::config() is called inside run() (in that order). Then the thread is created. The thread's function is internal and cannot be changed. You should adjust the semantics of these functions accordingly. When the thread is created, the function that is called is thread_o::entry_point(). This function does nothing more than simply calls process() and cleanup(). Both of these are virtual functions, intended to be redefined (reimplemnted) by you.
To be sure that a thread is no longer running: near the end of the thread's life, inside its "process()" function, it should call the member function check_finish(). This will kill the thread if it was instructed to via a call to terminate(). Such a call could be (actually need be) done outside the thread, from the main line of execution.
the thread does not itself call terminate(), from inside the thread itself.

member functions (primary)

thread_o()
SIGNATURE: thread_o()
SYNOPSIS:
creates a a new thread object. All internal variables are set to "empty" values. No threads are created within a constructor.
 

destructor
SIGNATURE: ~thread_o()
SYNOPSIS:
Virtual destructor. If the thread is running, wait() is called, so that the thread will terminate. Any corresponding existing thread is destroyed.
 

run()
SIGNATURE: int run ()
SYNOPSIS:
starts the thread. This will invoke operating system API functions that will then call the private member function entry_point(). That function will in turn execute run().
 

wait()
SIGNATURE: int wait (u_long max_msec = 0, int *pi = NULL)
SYNOPSIS:
this member function waits for the given thread to terminate. This function can be used within a thread, so that any given thread waits for another thread to terminate. if the thread is not running (e.g., already terminated), it returns immediately.
if the input parameter is greater than zero, the caller thread will block for that many milliseconds, if the thread does not return prior.
PARAMETERS

  • max_msec: [input] maximum time to wait, in milliseconds. if set to 0, it waits indefinitely
  • pi: [output] error indicator var. values:
    0: successful return.
    zErr_Unknown: unknown problem.
    zErr_Resource_TimeOut: time-out expired.
    zErr_Impossible_CaseValue; odd return value from OS.
 

sleep()
SIGNATURE: int sleep (count_t nsec)
SYNOPSIS:
this member function puts the thread to sleep the specified number of seconds (given by "nsec"). If the operating system does not support this function, it will return 1.
It is a public function - it can be called from your thread sub-class directly, or from the "callback" function invoked by thread_o::process() (calling it from the main thread will not put the thread to sleep). For example:

#include "z_thread.h"
class AppThread_o : public thread_o
{
public:
    AppThread_o();
    int process();
    // ..
};

static AppThread_o *p_th = NULL; void global_function();
int main (int argc, char *argv[]) { z_start(); z_finish(); return 0; } int AppThread_o::process() { p_th = this; global_function(); return 0; } void global_function() { p_th->sleep(5); }
Here, the point is that you can save a pointer to the thread object somewhere ("p_th = this;"), and then use it to access the thread's member functions. This may seem odd and even a bit convoluted, but it works.
WARNING: do not use this from outside the thread (such as another thread or the main-line control flow)
RETURNS:
0: the function call successfully put the thread to sleep
1: the 'sleep' functionality is not available
-1: error ocurred, or the thread does not exist.
 

msleep()
SIGNATURE: int msleep (count_t nmsec)
SYNOPSIS:
This puts the thread to sleep (assuming it is active) for the "nmsec" milliseconds (nmsec / 1000 seconds). If the operating system does not support this function, it will return 1.
WARNING: do not use this from outside the thread (such as another thread or the main-line control flow).
RETURNS:
0: the function call successfully put the thread to sleep
1: the 'sleep' functionality is not available
-1: error ocurred, or the thread does not exist.
TRAITS: This is a public function
 

terminate()
SIGNATURE: int terminate ()
SYNOPSIS:
this member function marks the thread for termination. It should be called from the thread, near the end of its life. This function does not actually forcefully terminate itself - it makes a request to be terminated. Thread destruction is done with check_finish().
 

config()
SIGNATURE: int config ()
SYNOPSIS:
this is a virtual function. It is called just prior to the creation of the actual thread. Its purpose is for your application to do any initialization specific to the thread instance.
 

cleanup()
SIGNATURE: int cleanup ()
SYNOPSIS:
this is a virtual function. It is called after the thread completes its processing (or is told to shut down, and check_finish() is called.
 

process()
SIGNATURE: int process ()
SYNOPSIS: this is an abstract virtual function, thus preventing the thread_o class in itself from being instantiated.
 

check_finish()
SIGNATURE: int check_finish ()
SYNOPSIS:
this member function will terminate the thread if a prior "terminate()" call has been done. It will call cleanup() just prior to the thread actually being destroyed. If this is the case, the thread code should return immediately. The caller code will know that by the check_finish() return code - it will be 1 if the thread has been destroyed.
RETURNS:
0: the thread is still alive
1: the thread has been terminated. the code should exit immediately.
 

warnings.
Currently the "sleep()" and "msleep()" member functions are not implemented on unix systms.

examples.
This example is a bare-minimum usage case. It creates 4 threads that write to the same file independently. Each thread recieves the next available name and an ID. They write their name and the event into the file 5 times. The write is enveloped by a semaphore, defining the critical section. The thread sleeps a random number of seconds (1 to 10) prior to each write.
This program can be expanded considerably, to dump to stdout, or to make the number of threads and iterations configurable.

#include "stdafx.h"
#include "z_func.h"
#include "z_mathsubs.h"
#include "z_string.h"
#include "z_thread.h"
#include "z_semaphore.h"

#define Num_Threads 4

semaphore_o *pg_mainlock = NULL;
static const char *names[] =
    { "Joe", "Betty", "Fred", "Darlene", "Jimmy", "Sarah", "Arnold", NULL };
static int is_taken[]  = { 0, 0, 0, 0, 0, 0 };
int next_ID = 0;

//......................................................................
class fwrite_thread_o : public thread_o
{
public:
    fwrite_thread_o () { }

protected:
    virtual int config    ();
    virtual int cleanup   ();
    virtual int process   ();

private:
    int write_to_file (int, int = -1);

    int idx, my_ID;
    string_o my_name;
};

//----------------------------------------------------------------------
int main (int argc, char *argv[])
{
    z_start();
    pg_mainlock = new semaphore_o;      // create the only semaphore
    fwrite_thread_o thr[Num_Threads+2]; // create 4 threads (& 2 extra)

    FILE *fp;
    fp = fopen ("myfile.txt", "w");     // create the target file,
    fclose (fp);                        // in the current directory

    int i;
    for (i = 0; i < Num_Threads; i++)
        thr[i].run();                   // THE CORE: run all the threads

    z_finish();
    return 0;
}

//----------------------------------------------------------------------
int fwrite_thread_o::config ()
{
    int i;
    for (i = 0; names[i] != NULL; i++)  // search for next available name
        if (is_taken[i] == 0)
            { is_taken[i] = 1; my_name = names[i]; my_ID = i; break; }
    write_to_file (0);                  // do thread's 1st/initial file write
    return 0;
}

//----------------------------------------------------------------------
int fwrite_thread_o::cleanup ()
{
    return write_to_file(2);            // just write a "goodbye" to file
}

//----------------------------------------------------------------------
int fwrite_thread_o::process ()
{
    double d; int ie, my_sec;

    for (idx = 0; idx < 5; idx++)       // each thread will write 5x
    {
        d = z_big_random_number(&ie);   // returns random number, 0 .. [big]
        my_sec = ((count_t) d) % 10;    // make it in [0..9]
        sleep (my_sec);                 // wait random # sec

        if (check_finish())             // check - instructed to die?
            return 0;                   // yes - exit out

        write_to_file (1, my_sec);      // write another line to the file
    }

    return 0;
}

//----------------------------------------------------------------------
int fwrite_thread_o::write_to_file (int code, int num_sec)
{
    int j;
    char b[800];
    string_o s;

    s = "ID #";                         // set up a pretty string to write
    z_int_to_str (my_ID, b);            // start with ID # (-> b)
    s += b;                             // s: "ID #5"
    s += " (";                          // s: "ID #5 ("
    s += my_name;                       // s: "ID #5 (Betty"
    for (j = my_name.size(); j < 8; j++)
        s += " ";                       // s: "ID #5 (Betty   "
    s += "); ";                         // s: "ID #5 (Betty   ); "

    if (num_sec >= 0)
    {
        s += "slept ";
        z_int_to_str (num_sec, b);
        s += b;
        if (num_sec < 10) s += " ";     // align xx (2) digits
        s += " seconds. ";      // s: "ID #5 (Betty   ); slept 2 seconds. "
    }

    s += "Action: ";
         if (code == 0) s += "START";
    else if (code == 1) s += "'processing'";
    else if (code == 2) s += "FINISH";
    s += "\n";

    pg_mainlock->enter();               // put a lock on the global resource
    FILE *my_fp = fopen ("myfile.txt", "a");
    if (my_fp == NULL)
        { std::cerr << "AARGH! can't open the file! I Die!\n"; return -1; }
    ::fputs (s.data(), my_fp);
    fclose (my_fp);
    pg_mainlock->leave();               // done, unlock the global resource

    return 0;
}

history.

Tue 02/19/2003: created {--AG}
Tue 03/11/2003: clean-up, comments added {--GeG}
Fri 04/01/2011: moving inlines into this file {--GeG}