The task consists of a memory (code, globals, heap), OS info, and threads. Each thread is a unit of execution, which consists of a stack and CPU state (i.e. registers). Multiple threads resemble multiple processes, except that multiple threads within a task use the same code, globals and heap. Thus, while two processes in Unix can only communicate through the operating system (e.g. through files, pipes, or sockets), two threads in a task can communicate through memory.
There are various primitives that a thread system must provide. Let's start with three basic ones. In this initial discussion, I am talking about a generic thread system. We'll talk about specific ones (such as the sun lightweight process library) later.
This says to create a new thread which runs the given procedure with the given arguments. Sometimes the arguments are omitted, and sometimes only one argument (a (void *)) is allowed. It returns a pointer to the new thread (which I'll call a thread control block).
This says to wait for the thread represented by tcb to finish executing. Often thread_join() returns an integer or a (void *) as its exit value. You can think of thread_join() as analogous to wait() in Unix it waits for the specified thread to complete, and gathers information about the thread's exit status.
In the thread system, some threads will be blocked (e.g. those calling thread_join()) and some will be ready to run. The ready ones will be on a ready queue. Thread_yield() says to put the currently running thread on the ready queue and run some other thread on the ready queue.
#include < lwp/lwp.h > #include < lwp/check.h > #include < lwp/lwpmachdep.h > #include < lwp/stackdep.h >And you have to link liblwp.a to your object files. (i.e. if your program is in main.c, you need to do the following to make your thread executable):
UNIX> cc -c main.c UNIX> cc -o main main.o -llwpThere's a lot of junk in the lwp library. You can read about it in the various man pages. Start with ``man 3l intro''. The three basic primitives described above are:
int lwp_create(tid, func, prio, flags, stack, nargs, arg1, ..., argn)
thread_t *tid;
void (*func)();
int prio;
int flags;
stkalign_t *stack;
int nargs;
int arg1, ..., argn;
int lwp_join(tid)
thread_t tid;
int lwp_yield(tid)
thread_t tid;
Yuck. lwp_create() is not too simple. You give it a function
func and
arguments (defined with nargs and arg1, ..., argnargn>),
but you must also
give it a priority, some flags, and a stack for it to use. A priority
of 1 and flags of 0 work fine. To allocate a stack, there are procedures
to help you out:
Call lwp_setstkcache(MINSTACKSZ*sizeof(stkalign_t), 4) at the beginning of your main() routine. This initializes 4 stacks and saves them. When you want a stack, you call
stkalign_t *s; s = lwp_newstk();Then you can use that stack for the lwp_create() call. Finally, lwp_create() puts the tcb info into the tid variable. This is what you use for lwp_join(). So, for example, suppose you want to fork off one thread that prints ``Hello world''. The following program will do it. (This is in hw.c)
#include < lwp/lwp.h >
#include < lwp/check.h >
#include < lwp/lwpmachdep.h >
#include < lwp/stackdep.h >
#include < stdio.h >
printme()
{
printf("Hello world\n");
}
main()
{
stkalign_t *s;
thread_t t;
lwp_setstkcache(MINSTACKSZ*sizeof(stkalign_t), 1);
s = lwp_newstk();
lwp_create(&t, printme, 1, 0, s, 0);
lwp_join(t);
}
Try copying hw.c to your home area, compiling it, and running it.
It should print out ``Hello world''.
The lwp library has the threads be non-preemptive. Thus, a thread does not relinquish control of the cpu unless it blocks or calls lwp_yield(). This means that you don't have to worry about synchronization like you would if you were running on a multiprocessor or a thread system which preemptively reschedules threads. In fact, you should be able to trace exactly how the print4 program should run.
main()
{
struct inputstruct *input;
struct outputstruct *output;
while(1) {
input = read_input();
if (input == NULL) exit(0);
output = process_input(input);
print_output(output);
}
}
It's not really important what read_input(), process_input() and
print_output() really do, except that they read input, process the input
and make output, and print the output respectively. I've defined them
as follows in
stuff.c:
UNIX> seqpc 1 2 3 4 Sum: 10 1 1 1 1 Sum: 4 1 1 1 1 1 Sum: 4 1 Sum: 1 a b c d Sum: 0Now, a standard way to turn seqpc.c into a threaded program is to separate threads do the producing (reading input and processsing it) and consuming (printing the output). Look at the program threadpc1.c. It does just this with three threads. The first one forks off a producer and consumer thread. Then the producer thread reads inputstructs, processes them, and puts the outputstruct into a dlist (doubly linked list). The consumer thread takes outputstructs from the dlist and prints them out.UNIX>
(the SELF is a thread_t that points to the calling thread)
Try copying this and running it:
UNIX> threadpc1 1 2 3 4Nothing happens. Hmmm. Type CNTL-D though:
UNIX> threadpc1 1 2 3 4 < CNTL-D > Sum: 10 UNIX>What's going on? Well, in read_input(), the thread blocks on the read() call (inside gets()). Since Unix doesn't support threaded system calls, this blocks the whole process (or task). So, even though the consumer thread can run, it doesn't because the producer thread blocks. Were we working on a true multiprocessor or on an operating system that actually supports threads (rather than a user-level library like this one), the consumer thread could run even though the producer thread is blocked. Thus, this wouldn't be a problem. However, there may be other problems though, which I'll talk about later.
Look at threadpc2.c. It puts a lwp_yield() call after the dl_insert_b() call, which lets the consumer thread run.
Now you get:
UNIX> threadpc2 1 2 3 4 Sum: 10 0 0 0 2 Sum: 2UNIX>
This is an important first lesson about threads. You have to know the parameters of your thread system. In particular: