Introduction to Multi-Threaded Programming

May 1st, 1999 by Brian Masney in

A description of POSIX thread basics for C programmers.
Your rating: None Average: 4.4 (23 votes)

The purpose of this article is to provide a good foundation of the basics of threaded programming using POSIX threads and is not meant to be a complete source for thread programming. It assumes the reader has a good strong foundation in C programming.

A thread is sometimes referred to as a lightweight process. A thread will share all global variables and file descriptors of the parent process which allows the programmer to separate multiple tasks easily within a process. For example, you could write a multi-threaded web server, and you could spawn a thread for each incoming connection request. This would make the network code inside the thread relatively simple. Using multiple threads will also use fewer system resources compared to forking a child process to handle the connection request. Another advantage of using threads is that they will automatically take advantage of machines with multiple processors.

As I mentioned earlier, a thread shares most of its resources with the parent process, so a thread will use fewer resources than a process would. It shares everything, except each thread will have its own program counter, stack and registers. Since each thread has its own stack, local variables will not be shared between threads. This is true because static variables are stored in the process' heap. However, static variables inside the threads will be shared between threads. Functions like strtok will not work properly inside threads without modification. They have re-entrant versions available to use for threads which have the format oldfunction_r. Thus, strtok's re-entrant version would be strtok_r.

Since all threads of a process share the same global variables, a problem arises with synchronization of access to global variables. For example, let's assume you have a global variable X and two threads A and B. Let's say threads A and B will merely increment the value of X. When thread A begins execution, it copies the value of X into the registers and increments it. Before it gets a chance to write the value back to memory, this thread is suspended. The next thread starts, reads the same value of X that the first thread read, increments it and writes it back to memory. Then, the first thread finishes execution and writes its value from the register back to memory. After these two threads finish, the value of X is incremented by 1 instead of 2 as you would expect.

Errors like this will probably not occur all of the time and so can be very hard to track down. This becomes even more of a problem on a machine equipped with multiple processors, since multiple threads can be running at the same time on different processors, each of them modifying the same variables. The workaround for this problem is to use a mutex (mutual exclusion) to make sure only one thread is accessing a particular section of your code. When one thread locks the mutex, it has exclusive access to that section of code until it unlocks the mutex. If a second thread tries to lock the mutex while another thread has it locked, the second thread will block until the mutex is unlocked and is once more available.

In the last example, you could lock a mutex before you increment the variable X, then unlock X after you increment it. So let's go back to that last example. Thread A will lock the mutex, load the value of X into the registers, then increment it. Again, before it gets a chance to write it back to memory, thread B gets control of the CPU. It will try to lock the mutex, but thread A already has control of it, so thread B will have to wait. Thread A gets the CPU again and writes the value of X to memory from the registers, then frees the mutex. The next time thread B runs and tries to lock the mutex, it will be able to, since it is now free. Thread B will increment X and write its value back to X from the registers. Now, after both threads have completed, the value of X is incremented by 2, as you would expect.

Now let's look at how to actually write threaded applications. The first function you will need is pthread_create. It has the following prototype:

int pthread_create(pthread_t *tid,
   const pthread_attr_t *attr,
   void *(*func)(void *), void *arg)

The first argument is the variable where its thread ID will be stored. Each thread will have its own unique thread ID. The second argument contains attributes describing the thread. You can usually just pass a NULL pointer. The third argument is a pointer to the function you want to run as a thread. The final argument is a pointer to data you want to pass to the function. If you want to exit from a thread, you can use the pthread_exit function. It has the following syntax:

void pthread_exit(void *status)
This will return a pointer that can be retrieved later (see below). You cannot return a pointer local to that thread, since this data will be destroyed when the thread exits.

The thread function prototype shows that the thread function returns a void * pointer. Your application can use the pthread_join function to see the value a thread returned. The pthread_join function has the following syntax:

int pthread_join(pthread_t tid, void **status)

The first argument is the thread ID. The second argument is a pointer to the data your thread function returned. The system keeps track of return values from your threads until you retrieve them using pthread_join. If you do not care about the return value, you can call the pthread_detach function with its thread ID as the only parameter to tell the system to discard the return value. Your thread function can use the pthread_self function to return its thread ID. If you don't want the return value, you can call pthread_detach(pthread_self()) inside your thread function.

Going back to mutexes, the following two functions are available to us: pthread_mutex_lock and pthread_mutex_unlock. They have the following prototype:

int pthread_mutex_lock(pthread_mutex_t *mptr)
int pthread_mutex_unlock(pthread_mutex_t *mtr)

For statically allocated variables, you must first initialize the mutex variable to the constant PTHREAD_MUTEX_INITIALIZER. For dynamically allocated variables, you can use the pthread_mutex_init function to initialize a mutex variable. It has the following prototype:

int pthread_mutex_init(pthread_mutex_t *mutex,
   const pthread_mutexattr_t *mutexattr)
Now we can look at actual code as shown in Listing 1. I have commented the code to help the reader follow what is being done. I have also kept the program very basic. It does nothing truly useful, but should help illustrate the idea of threads. All this program does is initiate 10 threads, each of which increments X until X reaches 4,000. You can remove the pthread_mutex_lock and unlock calls to further illustrate the uses of mutexes.

Listing 1. Example Program

A few more items need to be explained about this program. The threads on your system may run in the order they were created, and they may run to completion before the next thread runs. There is no guarantee as to what order the threads run, or that the threads will run to completion uninterrupted. If you put “real work” inside the thread function, you will see the scheduler swapping between threads. You may also notice, if you take out the mutex lock and unlock, that the value of X may be what was expected. It all depends on when threads are suspended and resumed. A threaded application may appear to run fine at first, but when it is run on a machine with many other things running at the same time, the program may crash. Finding these kinds of problems can be very cumbersome to the application programmer; this is why the programmer must make sure that shared variables are protected with mutexes.

What about the value of the global variable errno? Let's suppose we have two threads, A and B. They are already running and are at different points inside the thread. Thread A calls a function that will set the value of errno. Then, inside thread B, it will wake up and check the value of errno. This is not the value it was expecting, as it just retrieved the value of errno from thread A. To get around this, we must define _REENTRANT. This will change the behavior of errno to have it point to a per-thread errno location. This will be transparent to the application programmer. The _REENTRANT macro will also change the behavior of some of the standard C functions.

To obtain more information about threads, visit the LinuxThreads home page at http://pauillac.inria.fr/~xleroy/linuxthreads/. This page contains links to many examples and tutorials. It also has a link where you can download the thread libraries if you do not already have them. Downloading is necessary only if you have a libc5-based machine; if your distribution is glibc6-based, LinuxThreads should already be installed on your computer. The source code for threaded application that I wrote, gFTP, can be downloaded from my web site at http://www.newwave.net/~masneyb/. This code makes use of all concepts mentioned in this article.

Resources

Brian Masney is currently a student at Concord College in Athens, WV. He also works as a computer technician at a local computer store. In his spare time, he enjoys the outdoors and programming. He can be reached at masneyb@newwave.net.

__________________________


Special Magazine Offer -- Free Gift with Subscription
Receive a free digital copy of Linux Journal's System Administration Special Edition as well as instant online access to current and past issues. CLICK HERE for offer

Linux Journal: delivering readers the advice and inspiration they need to get the most out of their Linux systems since 1994.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Anonymous's picture

Tcp Client and Server

On June 21st, 2009 Anonymous (not verified) says:

How would you create a tcp server and client, whereby the server wants to be able to see any move made by the client and vice versa?[c-sharp]

Anonymous's picture

Did the first commenter

On May 21st, 2009 Anonymous (not verified) says:

Did the first commenter seriously use goto? GTFO and learn to program.

Eric Saint-Etienne's picture

Accessing the variable "x"

On March 16th, 2009 Eric Saint-Etienne (not verified) says:

Accessing the variable "x" outside the mutexed area is not really a good idea.
I would rather have used something like this:

void *mythread(void *data) {
start:
  pthread_mutex_lock(&count_mutex);
  if (x < 4000) {
    x++;
    printf("Thread ID%ld: X is now %d.\n", pthread_self(), x);
    pthread_mutex_unlock(&count_mutex);
    goto start;
  }
  else {
    pthread_mutex_unlock(&count_mutex);
  }
  pthread_exit(NULL);
}

This is one of the rare cases where a "goto" is useful ;-)

Thanks for the tutorial.

Anonymous's picture

Restructuring the increment loop

On November 5th, 2008 Anonymous (not verified) says:

How about this?


bool run=true;
while(run) {
pthread_mutex_lock(&count_mutex);
if(run=(x<4000) x++;
printf("Thread ID%ld: X is now %d.\n",
pthread_self(), x);
pthread_mutex_unlock(&count_mutex);
}

Anonymous's picture

I think changing the

On October 10th, 2008 Anonymous (not verified) says:

I think changing the increment line to

if (x < 4000) {x++};

would be better, otherwise the count goes higher than 4000 as the while check is outside the mutex. Otherwise, thanks for the example, it was very clear.

Antony's picture

code analyzer for multi threaded programming

On March 30th, 2008 Antony (not verified) says:

Errors in multi threaded application programming like Thread block, Dead lock and Race conditions can also be solved using static tools. Companies like Symbian , Juniper networks ,Research in Motion(Blackberry),Cisco are using Coverity Prevent, a Static analysis code inspection tool for analyzing source code for fixing defects. Coverity Prevent is also used by the Department of Homeland security to scan many open source projects. you can get more info at at http://www.Coverity.com

maria's picture

LinuxThreads and NPTL

On July 29th, 2007 maria (not verified) says:

Perhaps you would like to revisit
http://pauillac.inria.fr/~xleroy/linuxthreads/
It's stated that "LinuxThreads is now obsolete and is being replaced by NPTL."
Thank you for the introduction.

Anonymous's picture

pthreads is not obsolete.

On March 7th, 2008 Anonymous (not verified) says:

pthreads is not obsolete. it's just the implemention in glibc and kernel.

Anonymous's picture

Excellent

On February 17th, 2007 Anonymous (not verified) says:

This is my first MT program, this tutorial gave me a clear idea..wonderful..!!
ThanX a lot..!!

petrov9's picture

Yes

On October 4th, 2006 petrov9 (not verified) says:

The article helped me to understand.
Thanks...

Omer's picture

Compile error with the source code

On April 10th, 2006 Omer (not verified) says:

I think this line :

pthread_t tid[10];

Should be changed into :

pthread_t tids[10];

Because code uses "tids" instead of "tid" ... a small syntax error.

Thank you for the tutorial it helped a lot.

youssef's picture

The sample code is buggy

On November 14th, 2005 youssef (not verified) says:

Thanks for the tutorial,
I tried the sample code provided in this article. After a small change of the variable name tid to tids, I compiled the program with no errors. However, the result was not what I was expecting .Actually the variable X goes up to 4009 not the expected value 4000. I think it might be because the lock is inside the while loop..further stop condition should be implemented after the thread got the mutex.
thanks

Anonymous's picture

Re: Introduction to Multi-Threaded Programming

On September 9th, 2002 Anonymous says:

Probably should have covered condition variables.

Anonymous's picture

Re: Introduction to Multi-Threaded Programming

On June 13th, 2003 Anonymous says:

i need some more examples programmes for threads.could u send to my id?.my id is " panusuyadevi@yahoo.co.in "
thanku

Anonymous's picture

Re: Introduction to Multi-Threaded Programming

On July 7th, 2002 Anonymous says:

IT is a wonderfull sample...

I would like to know more about pthreads..

could pls assist me where i could get more information and samples --Jai

Anonymous's picture

Re: Introduction to Multi-Threaded Programming

On July 7th, 2002 Anonymous says:

IT is a wonderfull sample...

I would like to know more about pthreads..

could pls assist me where i could get more information and samples

Anonymous's picture

Re: Introduction to Multi-Threaded Programming

On December 21st, 2001 Anonymous says:

Wonderfully written. Convered all the basic issues with MT programming in just a page.

Anonymous's picture

Re: Introduction to Multi-Threaded Programming

On May 14th, 2002 Anonymous says:

Very well written. Simple yet precise.

Post new comment

Please note that comments may not appear immediately, so there is no need to repost your comment.
The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <pre> <ul> <ol> <li> <dl> <dt> <dd> <i> <b>
  • Lines and paragraphs break automatically.

More information about formatting options

Newsletter

Each week Linux Journal editors will tell you what's hot in the world of Linux. You will receive late breaking news, technical tips and tricks, and links to in-depth stories featured on www.linuxjournal.com.
Sign up for our Email Newsletter

Tech Tip Videos

From the Magazine

December 2009, #188

If last month's Infrastrucuture issue was too "big" for you then try on this month's Embedded issue. Find out how to use Player for programming mobile robots, build a humidity controller for your root cellar, find out how to reduce the boot time of your embedded system, and if you're new to embedded systems find out the basics that go into one. You can also read about the Beagle Board, the Mesh Potato and a spate of other interestingly named items. And along with our regular columns don't miss our new monthly column: Economy Size Geek.







Read this issue