Programming with Threads

Last month we looked at using pipes and FIFOs to communicate between concurrently running processes. However, there are occasions when you might want to run two pieces of code concurrently without the limitations of communicating through pipes. Perhaps you have two (or more) pieces of code that need to share a set of data or are constantly updating shared data structures. Pipes and FIFOs are not well-suited to handle this kind of situation because they would require each process to keep its own copy of the data and to communicate with all other processes when that data changes. Such an arrangement would cause a great deal of problems and would be very difficult to debug.

Last month we looked at using pipes and FIFOs to communicate between concurrently running processes. However, there are occasions when you might want to run two pieces of code concurrently without the limitations of communicating through pipes. Perhaps you have two (or more) pieces of code that need to share a set of data or are constantly updating shared data structures. Pipes and FIFOs are not well-suited to handle this kind of situation because they would require each process to keep its own copy of the data and to communicate with all other processes when that data changes. Such an arrangement would cause a great deal of problems and would be very difficult to debug.

Fortunately, Linux provides us with “threads,” a programming tool for handling situations like these. Threads allow two or more pieces of code that are running concurrently to share the same set of resources. In this article I will introduce threads and the basic functions of the pthreads library that are necessary to create multiple threads of execution in your applications.

Threads (sometimes referred to as “threads of execution”) differ from simultaneously running processes because they share the same pool of memory. Even if you have never explicitly programmed with threads before, all the programs you’ve written have implicitly contained a single thread (the thread that calls your main() function at the beginning of the program and exits when the code in main() is finished).

You can think of each new thread that you create as another main() function that is called when it is created. For each thread that is created, its main() function is run in parallel with all other currently running threads. It is up to the operating system to make sure that all of these threads get a specific amount of time to run. This is transparent to the programmer, and the program behaves as if the threads are actually running at the same time (on multi-processor systems, it is possible for two or more threads to actually be executing instructions simultaneously).

There are many situations where threads are the appropriate tools to use in your applications. Threads are useful when you need responsiveness in your main application while other tasks need to be completed at the same time. For example, a Web server may detect requests for pages and, once a request comes in, create a new thread to handle the request while continuing to check for new requests. This way, the server can be responsive to incoming requests even while another thread continues to handle the request that has come in. Threads are also useful when your program contains multiple agents that need to act in parallel. Later in this column, we’ll look at a sample program that uses threads to set up a simple ticket agency with one thread for each person selling tickets.

Some pthread Functions

If you have programmed with threads before, feel free to skip the rest of this paragraph. For the rest of you, a warning — programming with threads is hard. If you’ve never used threads before and have only written sequential programs (programs that have a single “thread” of execution that starts at the beginning of the main() function and continues until the end of the function), you will soon discover that having multiple functions in your program executing at the same time adds a level of complexity that almost everyone underestimates. It’s a good idea to make sure that threads are absolutely necessary before using them. If a solution exists without threads, it will almost certainly be easier to debug than its counterpart solution with threads. That being said…

In this article we’ll look at the three most basic functions used in threads programming. In future articles we’ll look at some more advanced functions that you can use to write effective threaded applications.

To create a thread, use the pthread_create() function. Its prototype, as defined in pthread.h, can be found in Figure One .




Figure One: Prototype of the pthread_create () Function


int pthread_create (pthread_t* thread, pthread_attr_t* attr,
void* (*start_routine)(void* data), void* data);

This function returns 0 on success, and the new thread is immediately started and run concurrently with the function that called pthread_create(). On failure, the function returns a non-zero value. See the man page of the function for more details about failure codes.

Let’s now discuss the parameters you must pass to the pthread_create() function. The first argument, thread, is a pointer to a thread structure (of type pthread_t) that represents the newly created thread. The pthread_create() function fills in the necessary data in the structure so that you can use it to reference the new thread in subsequent calls to other thread functions. The second argument, attr, represents the attributes that you wish to give the thread you are creating. I will discuss different thread attributes in a later article. For now, you can just pass NULL for this argument. The next argument, start_routine, is the function that you wish to run in your new thread. This function must have prototype:


void* thread_function (void* data);

Finally, the fourth parameter, data, is the parameter that is passed to the thread function, start_routine, when it is called. For an example of a simple program that uses pthread_create, take the following:

Imagine that we run a ticket sales agency. We have 100 tickets to sell to anyone who wants them and also have a number of ticket agents (more than 1) selling the tickets for us. The ticket agents sit at their computers, taking phone calls and registering the tickets they sell. We could model the ticket selling process with the C code:


int num_tickets_left = 100;

void* ticket_agent (void* not_used)
{
/* While there are still tickets left to
be sold, attempt to sell a ticket. */
while (num_tickets_left > 0)
{
int sold_ticket_p = answer_phone ();
/* If the person bought a ticket,
update the number of remaining
tickets. */
if (sold_ticket_p)
num_tickets_left–;
}

return NULL;
}

To create threads to sell tickets with five ticket agents, our main program would look something like Figure Two.




Figure Two: Creating Threads to Run ticket_agent ()


#include <pthread.h>

#define NUM_AGENTS 5
int main (int argc, char** argv)
{
pthread_t agents[NUM_AGENTS];
int i;

/* Create NUM_AGENTS threads, one for each agent. */
for (i = 0; i < NUM_AGENTS; i++)
if (pthread_create (&agents[i], NULL, ticket_agent, NULL) != 0)
fprintf (stderr, “Could not create thread %d\n”, i);
}

This program will create five threads, each one running the ticket_agent() function defined above. Immediately after each thread is created it starts executing, so the first ticket agent will start before the second ticket agent. Once main() creates all five threads, there will be a total of six threads executing: main itself and each of the five ticket agents. However, this program does not work exactly the way we’d like it to. Like any other program, once the main() function exits, the process stops, regardless of any threads that may still be running. After main() creates the five threads, there are no more instructions to be executed, so it exits. This is clearly no good, since we would like to exit only after all the threads have finished selling tickets.

The pthread_join() function solves the problem of main() exiting before the created threads do the work that we want them to. This function allows any thread to wait on another thread. Once the pthread_join() function is called on a thread, the caller is “joined” to that thread. This means its execution will be suspended until that thread has finished executing. The prototype for the pthread_join() function is:


int pthread_join (pthread_t th, void** thread_return);

It returns 0 on success and non-zero on failure. See the man page for a list of different error codes that pthread_ join() can return. This function takes two arguments — the thread with which to join (th) and a pointer to the return value (thread_return). The return value of the joined thread will be placed in this parameter as a method of communicating information. Note that only one thread may join any given thread. Also, a thread may not join itself. To augment our main() function to sell tickets instead of instantly exiting after creating the ticket agents, we simply add the code to join on all the ticket agents. The result is contained below:


int main (int argc, char** argv)
{
pthread_t agents[NUM_AGENTS];
void* return_values[NUM_AGENTS];
int i;
/* Create NUM_AGENTS threads, one for each agent. */
for (i = 0; i < NUM_AGENTS; i++)
pthread_create (&agents[i], NULL, ticket_agent, NULL);
/* Join on all the threads to wait for them to finish. */
for (i = 0; i < NUM_AGENTS; i++);
pthread_join (agents[i], &return_values[i]);
}

This program will create five ticket agent threads and wait to exit until they are all finished selling tickets. Go ahead and try this simple threads program. You’ll need to write a function to simulate the ticket agent taking a phone call. You may wish to have the ticket agent simulate a phone call by sleeping for a random amount of time. Also, if you make each agent print out a message when it sells a ticket, you’ll be able to track how many tickets are being sold. You might even want to pass each thread a number when you create it so you can distinguish between the threads selling the tickets.

The final function I wish to discuss is the very simple function pthread_exit. Its prototype is:


void pthread_exit (void* retval);

This function simply causes the thread that calls it to immediately exit. This is equivalent to the following statement:


return retval;

which is in the function that is passed to pthread_ create. While in this function, there is no difference between using return and pthread_exit. However, pthread_exit is useful when you wish the thread to finish instantly, even when you are not in the original thread function.

Now that we’ve looked at the thread functions, let’s return to the complexity involved in programming with threads and concurrent programming in general. If you try to run the above ticket agent program, you might notice that sometimes more than 100 tickets are sold. The ticket_agent() function is a simple one and will successfully sell 100 tickets if only one agent is running this function. However, if more than one ticket agent runs this function at the same time, there’s a serious bug in the code!

Imagine there are two ticket agents in our agency. Each one runs the ticket_agent() function, happily selling tickets to the people that request them. After a while, the number of tickets left creeps down to one. Each ticket agent checks to see if the number of tickets left is greater than 0. Each ticket agent sees that it is and then answers the phone. Since the value of num_tickets_left is 1 and both agents can decrease this value by 1, a ticket can be sold that doesn’t exist! If we had 10 ticket agents, nine extra tickets could be sold. Clearly, this is a bug in our program (unless we work for an airline!) and demonstrates one of the difficulties inherent in programming with multiple threads of execution.

As I mentioned before, programming with threads can make things much more complicated, and they should be used only when necessary. That said, threads are very useful and are required in many applications. Hopefully this article has whet your appetite for the more advanced threads programming features we’ll be looking at in future columns. Next time, we’ll look at how to avoid the aforementioned ticket selling problem as well as several other problems that exist in concurrent programming. In the meantime, happy hacking!



Benjamin Chelf is an author and engineer at CodeSourcery, LLC. He can be reached at chelf@codesourcery.com.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62