5.1 Hello World!
5.2 Blocking I/O
5.3 Producer/Consumer
5.4 Automata
1. Introduction
2. Rationale
3. API Overview
4. API
5. Examples
6. Related Work
7. Conclusion
8. Man Pages
|
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
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;
}
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;
}
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.
|