5. Examples

5. Examples

Browsing

Home: Fair Threads in C

Previous chapter: API
Next chapter: Related Work

Examples

5.1 Hello World!
5.2 Blocking I/O
5.3 Producer/Consumer
    Processing Values
    Main Function
5.4 Automata
    Preemption by an Event
    Two Threads Run in Turn

Chapters

1. Introduction
2. Rationale
3. API Overview
4. API
5. Examples
6. Related Work
7. Conclusion
8. Man Pages

5.1 Hello World!

The following code is a complete example, made of two threads run in the same scheduler.

#include "fthread.h"
#include <stdio.h>

void h (void *id)
{
   while (1) {
      fprintf (stderr,"Hello ");
      ft_thread_cooperate ();
   }
}

void w (void *id)
{
   while (1) {
      fprintf (stderr,"World!\n");
      ft_thread_cooperate ();
   }
}

int main (void)
{
  ft_scheduler_t sched = ft_scheduler_create ();

  ft_thread_create (sched,h,NULL,NULL);
  ft_thread_create (sched,w,NULL,NULL);

  ft_scheduler_start (sched);

  ft_exit ();
  return 0;
}
The program outputs Hello World! cyclically. Note the call of ft_exit to prevent the program to terminate before executing the two threads. Execution of linked fair threads is round-robin and deterministic: messages Hello and World! are always printed in this order.

Here is the typical way to produce executable code:

gcc -D_REENTRANT -o test test.c -lfthread -lpthread 

5.2 Blocking I/O

The following function ft_thread_read implements a non-blocking read I/O, using the standard blocking read function. The calling thread first unlinks from the scheduler, then performs the read, and finally re-links to the scheduler:

ssize_t ft_thread_read (int fd,void *buf,size_t count)
{ 
   ft_scheduler_t sched = ft_thread_scheduler ();
   ssize_t res;
   
   ft_thread_unlink ();
   
   res = read (fd,buf,count);
   
   ft_thread_link (sched);
   return res;
}
5.3 Producer/Consumer

One implements a producer/consumer example. There are 2 files, in and out, and a pool of threads that take data from in, process them, and then put results in out. A scheduler and an event are associated to each file; the event is generated to indicate that a new value is produded in the associated file.

file in = NULL, out = NULL;
ft_scheduler_t in_sched, out_sched;
ft_event_t new_input, new_output;

Processing Values

In order to process a value v, the calling thread first unlinks from in_sched. After processing, it links to out_sched in order to put the result in out, and finally, it re-links to in_sched. The procedure for processing a value is the following (for simplicity, values are of type int):
void process_value (int v)
{
  ft_thread_unlink ();
  < process v >
  ft_thread_link (out_sched);
  put (v,&out);
  ft_thread_generate (new_output);
  ft_thread_unlink ();
  ft_thread_link (in_sched);
}
The function run by the processing threads is:
void process (void *args)
{
  while (1) {
    if (size(in) > 0) {
      process_value (get (&in));
    } else {
      ft_thread_await (new_input);
      if (size (in) == 0) ft_thread_cooperate ()
    }
  }
}
The event new_input is used to prevent polling when no value is available from in. However, to test it as present does not necessary implies that a value is available: it could have been consumed by another thread. Thus, a call to ft_thread_cooperate is needed to avoid an infinite loop during the same instant, if new_input is tested as present while no value is actually available.

Main Function

Two threads are added to the system: one for producing new values, and the other for consuming results. The main function is the following:
int main (void)
{
   int i;
   ft_thread_t thread_array [MAX_THREADS]

   in_sched  = ft_scheduler_create ();
   out_sched = ft_scheduler_create ();
   
   new_input  = ft_event_create (in_sched);
   new_output = ft_event_create (out_sched);     

   for (i=0; i<MAX_THREADS; i++) {
      thread_array[i] = ft_thread_create (in_sched,process,NULL,NULL);
   }
  
   ft_thread_create (in_sched,produce,NULL,NULL);
   ft_thread_create (out_sched,consume,NULL,NULL);

   ft_scheduler_start (in_sched);
   ft_scheduler_start (out_sched);  

   ft_exit ();
   return 0;
}
5.4 Automata

Preemption by an Event

Here is the example of a one-state automaton, named killer, that preempts a thread when an event is present. The thread and the event are accessible with the macro ARGS.
DEFINE_AUTOMATON (killer)
{
   void **args = ARGS;
   ft_event_t event   = args[0]
   ft_thread_t thread = args[1]
   
   BEGIN_AUTOMATON
     
     STATE_AWAIT (0,event)
     {
        ft_scheduler_stop (thread);
     }
  
   END_AUTOMATON
}
A fair thread is created by:
ft_thread_t a = ft_automaton_create (sched,killer,NULL,args);
The difference with a standard thread created by ft_thread_create is that no new pthread is actually created by ft_automaton_create. The automaton is simply run by the scheduler's pthread. Thus, no pthread context switch is needed and execution is more efficient.

Two Threads Run in Turn

The following automaton switches control between two threads, according to the presence of an event. The automaton switch_aut has three states. The first state resumes the first thread to run (initially, both threads are suspended). The switching event is awaited in the second state, and then the threads are switched. The third state is similar to the second, except that the threads are exchanged.
DEFINE_AUTOMATON (switch_aut)
{
   void **args = ARGS;
   
   ft_event_t   event   = args[0]
   ft_thread_t  thread1 = args[1]
   ft_thread_t  thread2 = args[2]
   
  BEGIN_AUTOMATON
     
     STATE (0)
     {
        ft_scheduler_resume (thread1);
     }
     STATE_AWAIT (1,event)
     {
        ft_scheduler_suspend (thread1);
        ft_scheduler_resume  (thread2);
        GOTO(2);
     }
     STATE_AWAIT (2,event)
     {
        ft_scheduler_suspend (thread2);
        ft_scheduler_resume  (thread1);
        GOTO(1);
     }
     
  END_AUTOMATON
}
If a standard thread were used instead of an automaton, one supplementary pthread would be needed to perform the same task.

This page has been generated by Scribe.
Last update Tue Sep 2 16:54:13 2003