has a single thread of execution.

Team-FLY

12.2 Use of Threads to Monitor Multiple File Descriptors

Multiple threads can simplify the problem of monitoring multiple file descriptors because a dedicated thread with relatively simple logic can handle each file descriptor. Threads also make the overlap of I/O and processing transparent to the programmer.

We begin by comparing the execution of a function by a separate thread to the execution of an ordinary function call within the same thread of execution. Figure 12.1 illustrates a call to the processfd function within the same thread of execution. The calling mechanism creates an activation record (usually on the stack) that contains the return address. The thread of execution jumps to processfd when the calling mechanism writes the starting address of processfd in the processor's program counter. The thread uses the newly created activation record as the environment for execution, creating automatic variables on the stack as part of the record. The thread of execution continues in processfd until reaching a return statement (or the end of the function). The return statement copies the return address that is stored in the activation record into the processor program counter, causing the thread of execution to jump back to the calling program.

Figure 12.1. Program that makes an ordinary call to processfd has a single thread of execution.

graphics/12fig01.gif

Figure 12.2 illustrates the creation of a separate thread to execute the processfd function. The pthread_create call creates a new "schedulable entity" with its own value of the program counter, its own stack and its own scheduling parameters. The "schedulable entity" (i.e., thread) executes an independent stream of instructions, never returning to the point of the call. The calling program continues to execute concurrently. In contrast, when processfd is called as an ordinary function, the caller's thread of execution moves through the function code and returns to the point of the call, generating a single thread of execution rather than two separate ones.

Figure 12.2. Program that creates a new thread to execute processfd has two threads of execution.

graphics/12fig02.gif

We now turn to the specific problem of handling multiple file descriptors. The processfd function of Program 12.1 monitors a single file descriptor by calling a blocking read. The function returns when it encounters end-of-file or detects an error. The caller passes the file descriptor as a pointer to void , so processfd can be called either as an ordinary function or as a thread.

The processfd function uses the r_read function of Program 4.3 instead of read to restart reading if the thread is interrupted by a signal. However, we recommend a dedicated thread for signal handling, as explained in Section 13.5. In this case, the thread that executes processfd would have all signals blocked and could call read .

Program 12.1 processfd.c

The processfd function monitors a single file descriptor for input .

 #include <stdio.h> #include "restart.h" #define BUFSIZE 1024 void docommand(char *cmd, int cmdsize); void *processfd(void *arg) { /* process commands read from file descriptor */    char buf[BUFSIZE];    int fd;    ssize_t nbytes;    fd = *((int *)(arg));    for ( ; ; )  {       if ((nbytes = r_read(fd, buf, BUFSIZE)) <= 0)          break;       docommand(buf, nbytes);    }    return NULL; } 
Example 12.1

The following code segment calls processfd as an ordinary function. The code assumes that fd is open for reading and passes it by reference to processfd .

 void *processfd(void *); int fd; processfd(&fd); 
Example 12.2

The following code segment creates a new thread to run processfd for the open file descriptor fd .

 void *processfd(void *arg); int error; int fd; pthread_t tid; if (error = pthread_create(&tid, NULL, processfd, &fd))    fprintf(stderr, "Failed to create thread: %s\n", strerror(error)); 

The code of Example 12.1 has a single thread of execution, as illustrated in Figure 12.1. The thread of execution for the calling program traverses the statements in the function and then resumes execution at the statement after the call. Since processfd uses blocking I/O, the program blocks on r_read until input becomes available on the file descriptor. Remember that the thread of execution is really the sequence of statements that the thread executes. The sequence contains no timing information, so the fact that execution blocks on a read call is not directly visible to the caller. The code in Example 12.2 has two threads of execution. A separate thread executes processfd , as illustrated in Figure 12.2.

The function monitorfd of Program 12.2 uses threads to monitor an array of file descriptors. Compare this implementation with those of Program 4.14 and Program 4.17. The threaded version is considerably simpler and takes advantage of parallelism. If docommand causes the calling thread to block for some reason, the thread runtime system schedules another runnable thread. In this way, processing and reading are overlapped in a natural way. In contrast, blocking of docommand in the single-threaded implementation causes the entire process to block.

If monitorfd fails to create thread i , it sets the corresponding thread ID to itself to signify that creation failed. The last loop uses pthread_join , described in Section 12.3, to wait until all threads have completed.

Program 12.2 monitorfd.c

A function to monitor an array of file descriptors, using a separate thread for each descriptor .

 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void *processfd(void *arg); void monitorfd(int fd[], int numfds) {       /* create threads to monitor fds */    int error, i;    pthread_t *tid;    if ((tid = (pthread_t *)calloc(numfds, sizeof(pthread_t))) == NULL) {       perror("Failed to allocate space for thread IDs");       return;    }    for (i = 0; i < numfds; i++)   /* create a thread for each file descriptor */       if (error = pthread_create(tid + i, NULL, processfd, (fd + i))) {          fprintf(stderr, "Failed to create thread %d: %s\n",                          i, strerror(error));          tid[i] = pthread_self();       }    for (i = 0; i < numfds; i++) {       if (pthread_equal(pthread_self(), tid[i]))          continue;       if (error = pthread_join(tid[i], NULL))          fprintf(stderr, "Failed to join thread %d: %s\n", i, strerror(error));    }    free(tid);    return; } 
Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net