If a set of threads all need to read input from stdin
, it might be confusing for them to each issue independent prompt-and-read operations. Imagine that two threads each writes its prompt using printf
, and then each reads the response using gets — you would have no way of knowing to which thread you were responding. If one thread asks "OK to send mail?" and the other asks "OK to delete root directory?" you'd probably like to know which thread will receive your response. Of course there are ways to keep the prompt and response "connected" without introducing a server thread; for example, by using the flockfile and funlockfile functions to lock both stdin and stdout around the prompt-and-read sequences, but a server thread is more interesting — and certainly more relevant to this section.
In the following program, server.c, each of four threads will repeatedly read, and then echo, input lines. When the program is run you should see the threads prompt in varying orders, and another thread may prompt before the echo. But you'll never see a prompt or an echo between the prompt and read performed by the "prompt server."
7-9 These symbols define the commands that can be sent to the "prompt server." It can be asked to read input,
14-22 The request_t structure defines each request to the server. The outstanding requests are linked in a list using the next member. The operation member contains one of the request codes (read, write, or quit). The synchronous member is nonzero if the client wishes to wait for the operation to be completed (synchronous), or 0 if it does not wish to wait (asynchronous).
27-33 The tty_server_t structure provides the context for the server thread. It has the synchronization objects (mutex and request), a flag denoting whether the server is running, and a list of requests that have been made and not yet processed (first and last).
35-37 This program has a single server, and the control structure (tty_server) is statically allocated and initialized here. The list of requests is empty, and the server is not running. The mutex and condition variable are statically initialized.
43-45 The main program and client threads coordinate their shutdown using these synchronization objects (client_mutex and clients_done) rather than using pthread_join.
■ server.c part 1 definitions
1 #include
2 #include
3 #include "errors.h" 4
5 #define CLIENT THREADS 4 /* Number of clients */
6
7 #define REQ_READ 1 /* Read with prompt */
8 #define REQ WRITE 2 /* Write */
9 #define REQ_QUIT 3 /* Quit server */
10
11 /*
12 * Internal to server "package" — one for each request.
13 */
14 typedef struct request_tag {
15 struct request_tag *next; /* Link to next */
16 int operation; /* Function code */
17 int synchronous; /* Nonzero if synchronous */
18 int done_flag; /* Predicate for wait */
19 pthread_cond_t done; /* Wait for completion */
20 char prompt[32]; /* Prompt string for reads */
21 char text[128]; /* Read/write text */
22 } request_t;
23
24 /*
25 * Static context for the server
26 */
27 typedef struct tty_server_tag {
28 request_t *first;
29 request_t *last;
30 int running;
31 pthread_mutex_t mutex;
32 pthread_cond_t request;
33 } tty_server_t;
34
35 tty_server_t tty_server = {
36 NULL, NULL, 0,
37 PTHREAD MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
38
39 /*
40 * Main program data
41 */ 42
43 int client_threads;
44 pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;
45 pthread_cond_t clients_done = PTHREAD_COND_INITIALIZER;
Part 2 shows the server thread function, tty_server_routine. It loops, processing requests continuously until asked to quit.
25-30 The server waits for a request to appear using the request condition variable.