Examples Documentation Contact Downloads

Threads

Threads allow for parallel execution in the same way that traditional forked processes do, but with the possibility of sharing state with common objects. Grace brings you a powerful thread model that keeps most of the thread-related hairs out of your soup.

Creating a Thread Subclass

You define threads by subclassing the thread class. This class defines the virtual run method. The actual background task gets defined by overriding this virtual:

class timewriter : public thread
{
public:
    timewriter (void) : thread ("timewriter")
    {
    }
    
    ~timewriter (void) {}
    
    void run (void)
    {
        while (true)
        {
            timestamp now = core.time.now ();
            fs.save ("timestamp", "%s" %format (now));
            sleep (60);
        }
    }
};

int myApp::main (void)
{
    timewriter thr;
    thr.spawn ();
    while (true) sleep (60);
}

Using Locks

In situations where you need access to a common object from multiple thread, you need to wrap it up in a lock template class. Let's take a look at a nonsensical stretch of the timewriter concept above and collect an array of timestamps:

class myApp : public application
{
public:
    myApp (void) : application ("myapp") {}
    ~myApp (void) {}
    
    int main (void);
    void addtimestamp (timestamp ti);
    timestamp getlasttimestamp (void);

protected:
    lock<value> timestamps;
};

class timewriter : public thread
{
public:
    timewriter (myApp *a) : thread ("timewriter"), app (*a) {}
    ~timewriter (void) {}
    
    void run (void)
    {
        while (true)
        {
            app.addtimestamp (core.time.now());
            sleep (60);
        }
    }
    
protected:
    myApp &app;
};

void myApp::addtimestamp (timestamp ti)
{
    exclusivesection (timestamps)
    {
        timestamps.newval() = ti;
    }
}

timestamp myApp::getlasttimestamp (void)
{
    timestamp res;
    sharedsection (timestamps)
    {
        res = timestamps[-1];
    }
    return res;
}

int myApp::main (void)
{
    timewriter T1 (this);
    timewriter T2 (this);
    
    T1.spawn ();
    T2.spawn ();
    
    while (true)
    {
        sleep (10);
        fout.writeln ("%s" %format (getlasttimestamp()));
    }
    return 0;
}

The exclusivesection and sharedsection macros are not the only access to a lock. They can only deal with lock objects that are local to the class. Using them encourages you to keep locking logistics close to the class actually affected. If, however, you have a compelling need to twiddle with a lock from outside a class, you can use the lockr, lockw and unlock methods and the lock::o member containing the lock-wrapped object.

Sending and Handling Events

Thread objects are blessed with an eventq mechanism that allows other threads to send them value objects as events. Let's revise our timestamp writing efforts:

class timewriter : public thread
{
public:
    timewriter (void) : thread ("timewriter") {}
    ~timewriter (void) {}
    
    void run (void)
    {
        value ev;
        bool shouldrun = true;
        
        while (shouldrun)
        {
            ev = waitevent ();
            caseselector (ev.type())
            {
                incaseof ("write") :
                    fs.save ("timestamp", "%s" %format (core.time.now()));
                    break;
                
                incaseof ("shutdown") :
                    shouldrun = false;
                    break;
                    
                defaultcase : break;
            }
        }
    }
};

int myApp::main (void)
{
    timewriter T;
    T.spawn ();
    T.sendevent ("write");
    T.sendevent ("shutdown");
    sleep (10);
    return 0;
}

You can send any extra data by feeding a value as the second argument to sendevent, it will end up — sans its original type in the value return from waitevent.

Synchronization with Conditionals

The shutdown logic in the previous example was actually kind of shoddy. The conditional class can help you synchronize events like the shutdown example:

class timewriter : public thread
{
public:
    timewriter (void) : thread ("timewriter") {}
    ~timewriter (void) {}
    
    void run (void)
    {
        ...
                incaseof ("shutdown") :
                    shouldrun = false;
                    shutcond.broadcast ();
                    break;
                    
                defaultcase : break;
        ...
    }
    
    void shutdown (void)
    {
        sendevent ("shutdown");
        shutcond.wait ();
    }
    
    conditional shutcnd;
};

int myApp::main (void)
{
    timewriter T;
    T.spawn ();
    T.sendevent ("write");
    T.shutdown ();
    return 0;
}

Grouped Threads

If you're looking at a job load that needs a load of work spread among a number of worker threads, the threadgroup and groupthread classes can be useful:

class output : public thread
{
public:
    output (void) : thread ("output") {}
    ~output (void) {}
    
    void run (void)
    {
        while (true)
        {
            value ev = waitevent ();
            if (ev.type() == "shutdown") return;
            fout.writeln (ev["text"]);
        }
    }
};
    
class calcworker : public groupthread
{
public:
    calcworker (threadgroup &grp, output &o)
        : groupthread (grp), out (o)
    {
        spawn ();
    }
    ~calcworker (void)
    {
    }
    
    void run (void)
    {
        while (true)
        {
            value ev = waitevent ();
            if (ev.type() == "shutdown") return;
            int res = ev["left"] + ev["right"];
            out.sendevent ("print", $("text", res));
        }
    }
    
protected:
    output &out;
};

int myApp::main (void)
{
    output O;
    threadgroup G;
    calcworker w1 (G, O);
    calcworker w2 (G, O);
    calcworker w3 (G, O);
    
    w1.sendevent ("calc", $("left",21)->$("right",21));
    w2.sendevent ("calc", $("left",10)->$("right",32));
    w3.sendevent ("calc", $("left",3)->$("right",66));
    G.broadcastevent ("shutdown");
    sleep (10);
    return 0;
}

Thread-local Storage

Sometimes it's useful to keep an object around that is unique to each individual thread. The perthread template is a pretty useful candidate for this. The logic behind the configdb class and the current working directory kept by the filesystem are examples. Here's how it works:

class doodad
{
public:
    doodad (void) {}
    ~doodad (void) {}
    
    int getmagic (void)
    {
        int res = magicint.get();
        if (! res) magicint = res = rand();
        return res;
    }
    
protected:
    perthread<int> magicint;
};

If you want a class to be available within the perthread context, implementing a functioning operator= is necessary.

Previous Chapter Table of Contents Next Chapter