next up previous contents index
Next: 10 List of available Up: $FILE Previous: 8 Question files   Contents   Index

Subsections


9 Threads

This version of the command interpreter library has been tested with two implementations of threads :

- linuxthreads version 0.71 (see http://pauillac.inria.fr/~xleroy/linuxthreads)

- The thread library that comes with libc6


Threads are working well with linuxthreads library (with libc5) , but not with the last versions of GNU pth (I don't know why). The threads are detached. Joinable threads work also, but with the following problems :

- memory leaks

- After 1024 threads have been created and finished, it is impossible to create new ones.

Threads provided with libc6 work also, but only joinable ones. For threads to be joinable, the file interp.c must be modified as follows : in line 2111 replace PTHREAD_CREATE_DETACHED with PTHREAD_CREATE_JOINABLE.


The only functions of the thread library which are used in the command interpreter library are : pthread_attr_init, pthread_attr_setdetachstate, pthread_create, pthread_mutex_init, pthread_mutex_lock and pthread_mutex_unlock.


9.1 Creation of threads

A program can be executed as a thread by using the command thread. For example

- interpcom -> thread th_0 prog0

will execute the program prog0 (which must be already loaded). Here th_0 is the name of the thread. The command interpreter can then receive and execute other commands. It is of course possible to execute several threads from the main stream or from other threads. The thread is finished when the corresponding program is. If the thread program does not run silently, the successive instructions as well as the messages will be printed. It is possible to give arguments to the program. For instance :

- interpcom -> thread th_0 prog0 arg1 arg2 arg3

The necessary informations that are used to run programs are stored in a flow_data structure

typedef struct _flow_data {
    int              MODE_FONCT_;
    int              I__COM_CUR;
    int              I_LIGNE_CUR;
    int              I_LEC_CUR;
    int              I_SPEED;
    char            *HCOM;
    int              IS_COM;
    int              PRLEVEL;
    int              IND_COM;
    char            *_H_LIGNE;
    char           **_ARGV_C;
    int              _ARGC_C;
    char            *CMDX;
    int              CURVOICE;
    int              HORLOGE;
    long             I_TIME_X;
    long             _I_TIME_X;
    int              PR_COM;
    int              IX_COM;
    FILE           **INP;
    int             *ARGC_X;
    argv_t          *ARGV_X;
    FILE            *MON_FILE;
    int              I_COM;
    char           **COM_PREC;
    XVARIABLE       *VARS;
    double	    *VARS2;
    char            *name;
    int              used;
    int              thread;
    char           **extra;
    char	   **h;
    char            *var;
    int              num;
    int              pause;
    int              mutex_obj;
    int		     parse_com;
    int		     kill;
    int		     n_instr;
} flow_data;

Each thread is associated to such a structure. The command interpreter itself (which receives the commands of the user) is also considered as a thread (the main thread) and uses a flow_data structure.

For instance :

- the member MODE_FONCT_ is the running mode in which the thread program is running.

- the member MON_FILE is the monitor file (if any) that is used.

- the member name is the name of the thread.


Four other commands are related to threads :

listt will give the list of all the running threads. For example, if the preceeding thread th_0 is running, we obtain

- interpcom -> listt
0 ------| main
1 ------| th_0

(thread number 0 is the main thread).


pause can be used to stop the execution of a thread. For instance

- interpcom -> pause th_0 1

will stop the execution of the thread th_0. More precisely the program will stop just after the instruction that was executed when the pause command was given. It is possible to continue the execution of the thread with the same command, with argument 0 :

- interpcom -> pause th_0 0

The command pause will not be accepted by the interpreter if only one thread (i.e. the main thread) is running.


wait can also be used to stop the execution of a thread, depending on the value of a global variable. For instance

- interpcom -> wait th_0 _x

will stop the execution of the thread th_0 if the global variable _x is undefined or if its value is less than or equal to zero. If its value becomes positive (with an instruction _x=1 for example, in another thread) then the thread th_0 will go on. As for pause, wait will not be accepted if only one thread is running.


kill can be used to kill a thread. For instance

- interpcom -> kill th_0

will kill the thread th_0. More precisely the program will stop just after the instruction that was executed when the kill command was given.


9.2 Local and global variables

Each running thread has its own set of variables of the expression evaluator. Only variables whose name begins with a _ (underscore character) or hidden variables (cf. 7) are global and can be used by all threads with the same meaning. So the same variable names can be used in different threads and contain distinct values in distinct threads, provided that their names does not begin with a _ . The global variables may be used by threads to communicate with each other.


9.3 Mutexes

The internal functions of the command interpreter that deal with threads, objects, string variables, numeric variables (of the expression evaluator), question files, conditions and files are protected by mutexes. This means for instance that a function (or two functions) using objects cannot be run simultaneously by two threads. This is done for instance to prevent a thread from destructing or creating an object while another one is reading the list of objects. I hope that I have put enough mutexes to avoid such situations. To avoid inconsistencies, the commands load and delprog used to load and delete programs can be used only in the main thread.


9.4 Programming with threads

9.4.1 How to retrieve the parameters of the current thread

Many useful functions of the command interpreter need to know what is the thread from which they are called, i.e. what is the corresponding flow_data structure. For instance the functions convert_int and convert_float, that are used to parse numerical arguments of commands, need that, because they can use local variables, i.e. variables that are specific to a thread (cf. 9.2). In a function corresponding to a command a pointer the flow_data structure is contained in argv[-1]. For instance

int MyCom_cmd(int argc, char *argv[])
{
    flow_data    *flow_interp;

    flow_interp = (flow_data *) argv[-1];
    .
    .
    .
}

If a command is emulated inside a function it is better to send it a pointer to the current flow_data structure. For instance if we want to emulate a command corresponding to the function com_cmd, which needs two arguments, we proceed like this :

    char   *k[4];    /* will contain the arguments */

    k[0] = (char *) flow_interp  /* the pointer to the flow_data structure */
/* Then we fill k with the other arguments, the first being the name of
   the emulated command */
       .
       .
       .
/* Then we call the command */
   com_cmd(3, k + 1);
       .
       .
       .


9.4.2 How to define thread-specific parameters

The flow_data structure has a member which can be used to define thread-specific properties :

    char           **extra;

When a thread is created, an array of 100 character strings is allocated but all the strings are NULL, except the first one which is used in the command interpreter. The second string, flow_interp->extra[1] is kept for further use by the command interpreter. The next 98 strings can be used in applications.

We will now explain how flow_interp->extra[0] is used, assuming that flow_interp is a pointer to the current flow_data structure.


flow_interp->extra[0] is a pointer to a Misc_Func structure. This structure has 3 members which are functions :

typedef void    (*pfi_print)(flow_data *, char *);
typedef int     (*pfi_ev)(char *, double *, int *, flow_data *);
typedef double  (*pfi_conv)(char *, flow_data *);

typedef struct MISC_FUNC {
    pfi_print    Print;
    pfi_ev       Ev;
    pfi_conv     Conv;
} Misc_Func;

The default Misc_Func structure that flow_interp->extra[0] points to has the following members : NULL, Evaluate and convert_float. Let

Mi = (Misc_Func *) flow\_interp->extra[0];

If Mi->Print is not NULL then the function print that is used by the interpreter to print messages will send its output to this function instead of using printf. Recall that this function must be a pfi_print as defined in interp.h :

typedef void    (*pfi_print)(flow_data *, char *);

This can be for instance a function that prints the char* in argument on some window. Assume that we use

void My_print_function(flow_data *flow_interp, char *pr);

Then a command that tells the interpreter to print using this function would contain something like this :

    Misc_Func      *Mi;    

    Mi = (Misc_Func *) flow\_interp->extra[0];
    Mi->Print =  My_print_function;

Then the output of all the subsequent commands (in the thread) will be sent to
My_print_function (provided that print is used to produce messages).


The member Mi->Ev is used in the function convert_float in interp.c. Its default value is

int  Evaluate(char *h, double *res, int *dum, flow_data *flow_interp)

This function sends the character string h in the expression evaluator and the result of the evaluation is stored in res. The main reason to change Mi->Ev would be to use another expression evaluator.


The member Mi->conv is used when the instruction [ is used (cf. 4.3.4). In this case all subsequent instructions are sent directly to the expression evaluator, until the instruction ] is reached. The default value of Mi->conv is

double  convert_float(char *arg, flow_data *flow_interp)

The main reason to change Mi->conv would be to use another expression evaluator or even a new interpreter between the instructions [ and ].


Now we explain how to define thread-specific parameters in applications. For this the strings flow_interp[2] to flow_interp[98] can be used. They are initially NULL. The user of the command interpreter library must provide two functions :

void  init_thread_param(flow_data *);
void  clean_thread_param(flow_data *);

The first one is called each time a new thread is created (in particular the main thread). The second one is called when the thread is finished. The argument is the corresponding thread.

If the application needs no thread-specific parameters, these functions can be empty (see the file test/main.c) but they must be defined somewhere.

Now we give an example. Suppose that in our application we need a thread-specific array of 4 integers. We can do like this :

void
init_thread_param(flow_data *flow_interp)
{
    int    *I;
    
    I = (int *) malloc((size_t) 4 * sizeof(int));
    flow_interp->extra[2] = (char *) I;
    
 /* Now could be given some initialization of the array I */
    .
    .
    .
}

void
clean_thread_param(flow_data *flow_interp)
{
    int    *I;

    I = (int *) flow_interp->extra[2];
    free(I);
}

In a command, the thread-specific array can be retrieved as follows :

int
Xcom(int argc, char *argv[])
{
    int         *I;
    flow_data   *flow_interp;
    
    flow_interp = (flow_data *) argv[-1];
    I = (int *) flow_interp->extra[2];
    .
    .
    .
}


next up previous contents index
Next: 10 List of available Up: $FILE Previous: 8 Question files   Contents   Index
jmdr 2001-12-07