10 - Designig parallel applications using FLTK.

This chapter discusses using FLTK for your parallel applications.

General discussion

First let us call a parallel application - a single process that has several (may be just one) light-weight (l/w) processes,the processes that use the same address space. Using above termimology we can say that FLTK has one GUI-l/w-process and several, may be none, extra l/w-processes that can communicate with each other. The application quits only after all l/w-processes finish. The general concept of parallel GUI-application is to keep all time-taking work (such as serious computations or waiting for a network socket) in extra l/w-processes, let us call such l/w-process a brain ;-) . A brain can send special messages to GUI-l/w-process, the GUI-l/w-process queue them and process them one-by-one, let as say in this situation that a brain requests an audience. A brain should request an audience when it wants to do any work that, because of the FLTK design, should be done from GUI-l/w-process.

Example (Counter.cxx)

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Brain.H>

#include <stdio.h>

class Counter : public Fl_Output,
                public Fl_Brain, public Fl_Secretary {
public:
     Counter(int X, int Y, int W, int H, char *L=0)
     :Fl_Output(X,Y,W,H,L) {
// Beautify
          color(FL_WHITE);
          selection_color(FL_GREEN);

          labeltype(FL_EMBOSSED_LABEL);
          labelfont(FL_COURIER);
          labelsize(20);
          labelcolor(FL_BLUE);

          align(FL_ALIGN_TOP_LEFT);

          textsize(20);
          textfont(FL_COURIER);

          value("0");
     };

// This function is called by GUI-thread in response to audience() call
     static void update(void *d) {
          Counter *c=(Counter *)d;
          char message[50];
          sprintf(message, "%d", (int)(c->i));
          c->value(message); // draw() is called automatically
// In this programm we want to wait for audience to be executed
          c->report();
     };
        
     static void controller_cb(Fl_Widget *w, void *) {
          Fl_Return_Button *r=(Fl_Return_Button *)w;
          Counter *c = (Counter*)(w->user_data());
          static int relaxed=1;
          if(relaxed^=1) {
               r->label("Proceed");
               r->labelcolor(FL_GREEN);
               c->relax();
          } else {
               r->label("Relax");
               r->labelcolor(FL_RED);
               c->proceed();
          }
          r->redraw();
     };
        
protected:
// Child thread code goes here.
     void action() {
          rank(0); // Choose the lowest priority
          for(i=0;;) {
               i++;
// This asks fltk to call update() member-function from GUI-thread                      
               audience(update, this);
// Wait for the update() metod
               expect();
          }
     };
private:
     short i;
};

int main(int argc, char *argv[])
{
     Fl::args(argc, argv);

// It is OK to make toplevel window a local variable
     Fl_Double_Window window(255, 115, "Counter");

     Counter counter(10, 25, 235, 35, "Counter:");
     Fl_Group::current()->resizable(&counter);

     Fl_Return_Button controller(65, 75, 125, 35, "Proceed");
     controller.labeltype(FL_ENGRAVED_LABEL);
     controller.labelfont(FL_TIMES_BOLD);
     controller.labelsize(20);
     controller.labelcolor(FL_GREEN);
     controller.callback(Counter::controller_cb, (void*)(&counter));

// Don't allow user to make window smaller that this size.
     window.size_range(255,115);
// That's all. ;-)
     window.end();

// argc, argv are passed so that command line parameters
// (geometry, etc.) are
     window.show(argc, argv);
        
     return Fl::run();
}
Sreenshot:

It is very simple. When you press on the button "Proceed" it changes its label to "Relax" and starts to count, when you press on "Relax" the counter stops and the button changes its label to "Proceed".

Explanation

In this example the brain waits for the GUI-l/w-process to process an audience request. But we could change the code so that the brain just send an audience request and continue its job. Please note rank(0) -this is very usefull if a brain has to make a long-time computations (not relax()'ing).

Example (Counter_lock.cxx)

class Counter : public Fl_Output,
                public Fl_Brain, public Fl_Lock {
// Please note we inherit Fl_Lock here,not Fl_Secretary
public:
// .....

// This function is called by GUI-thread in response to audience() call
     static void update(void *d) {
          Counter *c=(Counter *)d;
          char message[50];
          int i; // local copy!
          { Fl_Guard g(*c); i=c->i; }
          sprintf(message, "%d", i);
          c->value(message); // draw() is called automatically
          // no c->report(); !
     };

// .....

// Child thread code goes here.
     void action() {
//        rank(0); no need in the lowest priority
          for(int i=0;;) { // local copy!
               i++;
               { Fl_Guard g(*this); this->i=i; }
// This asks fltk to call update() member-function from GUI-thread                      
               audience(update, this);
//             no expect();!
// Relax just for 1/50 part of a second.
               relax_sec(1./50.);
          }
     };
// .....

Explanation

Please note that we use a lock here, because a variable i is accessed by two l/w-processes (GUI-l/w-process and a brain(Counter)). It is trivial to make a lock.Fl_Guard constructor locks a lock and deconstructor unlocks, regardless how we quited a block where we declared an Fl_Guard. In fact this style is mostly used in real applications.

Appendix: description of classes & methods.


class Fl_Brain

This provides a facade for controlling a brain.

Include Files

Methods

protected: Fl_Brain::Fl_Brain()

Does nothing. Please note that it is protected.

protected: virtual Fl_Brain::~Fl_Brain()

Destroys the brain by calling a shock() method.

protected: virtual void Fl_Brain::action()=0

This is an pure virtual method that should be defined in an implementation of brain (Fl_Brain subclass). This method is called when proceed() is called for the first time.

public: bool Fl_Brain::alive() const

Returns true if the brain was proceed()'ed, action() did not return and the brain wasn't shock()'ed. In the other case it returns false.

protected:
void Fl_Brain::audience(Fl_Widget *w, int event)
void Fl_Brain::audience_d(Fl_Widget *w, int dmask=FL_DAMAGE_ALL)
void Fl_Brain::audience(void (*cb)(int), int event=0)
void Fl_Brain::audience(void (*cb)(void*), void *args=NULL)

These are the most important methods. They send a request to the main GUI-l/w-process and it does the following as soon as possible (respectively):

public: void Fl_Brain::follow()

Makes the calling l/w-process wait for the brain death. It is safe to call this on !alive() brain.

public: bool Fl_Brain::follow(int n)
public: bool Fl_Brain::follow_sec(double t)

These methods makes a calling l/w-process to wait for the brain death (like follow()), but if the brain does not finish in n milliseconds (t seconds) they return false (if the brain was follow()'ed the return-value is true). It is safe to call this method on !alive() brain.

public: void Fl_Brain::proceed()

If called for the first time - it starts the brain (calls action()). If the brain was relax()'ed this methos makes it to continue.
Please note that most of Fl_Brain methods (exept Fl_Brain(), ~Fl_Brain(), follow(), follow_sec(), alive()) are only valid after proceed() was called at least onece.

public: double Fl_Brain::rank()
public: void Fl_Brain::rank(double)

Sets (gets) the priority (rank) of the brain Please note that under Linux rank() always returns 0.5 and it is only possible to decrease brain's rank(). It is a good idea to rank(1./5.); a time-taking l/w-process.

public: void Fl_Brain::relax()

Makes the brain to suspend execution until anyone calls proceed() method. A brain can call this from it's body, so relax()'ing itself.

public: static void Fl_Brain::relax(int n)
public: static void Fl_Brain::relax_sec(double t)

Makes the calling l/w-process to stop for n milliseconds (t seconds). Please note that these methods are static so a call like Fl_Brain::relax_sec(1.0); will work.

public: void Fl_Brain::shock()

Terminates the brain immediatly. No cleanup is done. All follow()'ing l/w-processes are resumed.

class Fl_Lock

This class provides a facade for controlling a lock. You can use a lock for mutual-exclusion synchronization. Lock is always recurcive. Once lock was hire()'d it should be fire()'d by the same brain.

Include Files

Methods

public: Fl_Lock::Fl_Lock()

Creates a lock (system)-object.

public: Fl_Lock::~Fl_Lock()

Destroys the lock (system)-object.

public: void Fl_Lock::hire()

Locks a lock so that all other brains that call this method with the same lock are suspended until a brain that hire()'d a lock fire()'s it.

public: void Fl_Lock::fire()

Releases a lock: makes a first brain that hire()'d this lock and was suspended to continue its execution. Please note that a lock should be fire()'d by the same brain that hire()'d it. This is not checked on some systems (ex. Linux) but you should follow this to keep your application portable.

class Fl_Guard

This is a helper class, but it is very usefull for managing locks.

Include Files

Methods

public: Fl_Guard::Fl_Guard(Fl_Lock & l)

Hire()'s a lock l.

public: Fl_Guard::~Fl_Guard()

Fire()'s a lock l. Please note that this method is called automatically when you exit the block. It is useful to create a static Fl_Lock object and auto Fl_Guard
{
     static Fl_Lock l;
     Fl_Guard g(l); // variables are auto by default

     //..... syncronized code.
}
It is also usefull to inherit Fl_Lock
class My : public Fl_Lock, Fl_Brain , ..... {
     //....
     {
          Fl_Guard g(this);

          // syncronized code.
     };
};

class Fl_Secretary

This class provides a facade for managing a secretary. A secretary is mostly usefull for brain<->brain and brain<->GUI-l/w-process communication.

Include Files

Methods

public: Fl_Secretary::Fl_Secretary()

Creates a secretary (system)-object.

public: Fl_Secretary::~Fl_Secretary()

Destroys the secretary (system)-object.

public: void Fl_Secretary::expect()

MAkes a calling brain to wait untill someone report()'s it or broadcast()'s.

public: bool Fl_Secretary::expect(int n)
public: bool Fl_Secretary::expect_sec(double t)

Makes a calling brain to wait untill someone report()'s it or broadcast()'s, in this case true is returned, a calling brain is also resumed after n milliseconds or t seconds pass, in this case false is returned.

public: void Fl_Secretary::report()

Makes a first brain that expect()'d this secretary and was suspended to continue its execution.

public: void Fl_Secretary::broadcast()

Makes all brain that expect()'d this secretary and were suspended to continue their execution.
Licence: LGPL. Copyright (c) 1999 by Yaroslav Volovich (yaroslav_v@mail.ru)
(http://volovich.da.ru) (27 September 1999)