Introduction to Multi-Threaded Programming

A description of POSIX thread basics for C programmers.

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.

______________________

Comments

Comment viewing options

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

there are errors when linking

shindow's picture

shindow@shindow-laptop:~/code$ gcc thread.c -o thread
/tmp/ccXY7Wqj.o: In function `main':
thread.c:(.text+0x40): undefined reference to `pthread_create'
thread.c:(.text+0x6e): undefined reference to `pthread_join'
collect2: ld returned 1 exit status

i check it at :http://stackoverflow.com/questions/1662909/undefined-reference-to-pthread-create-in-linux-c-programming

so gcc -pthread thread.c -o thread is correct

Excellent

Anonymous's picture

Hi,

First of all thanks for this tutorial. Its simple and very clear.
All mistakes in the sample example were already mentioned by others :)
Even I used

while(1) {
pthread_mutex_lock(&count_mutex);
if(x == 4000) {
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
return;
}
x++;
printf("Thread ID%ld: X is now %d.\n",
pthread_self(), x);
pthread_mutex_unlock(&count_mutex);
}

which will ensure x value to be 4000. See I got it from your tutorial :D.
Thanks again.

Just Add

Anonymous's picture

The follow link is a interesting guide:

https://computing.llnl.gov/tutorials/pthreads/

Tcp Client and Server

Anonymous's picture

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]

Did the first commenter

Anonymous's picture

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

Accessing the variable "x"

Eric Saint-Etienne's picture

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.

Restructuring the increment loop

Anonymous's picture

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);
}

I think changing the

Anonymous's picture

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.

code analyzer for multi threaded programming

Antony's picture

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

LinuxThreads and NPTL

maria's picture

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.

pthreads is not obsolete.

Anonymous's picture

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

Excellent

Anonymous's picture

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

Yes

petrov9's picture

The article helped me to understand.
Thanks...

Compile error with the source code

Omer's picture

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.

The sample code is buggy

youssef's picture

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

Re: Introduction to Multi-Threaded Programming

Anonymous's picture

Probably should have covered condition variables.

Re: Introduction to Multi-Threaded Programming

Anonymous's picture

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

Re: Introduction to Multi-Threaded Programming

Anonymous's picture

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

Re: Introduction to Multi-Threaded Programming

Anonymous's picture

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

Re: Introduction to Multi-Threaded Programming

Anonymous's picture

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

Re: Introduction to Multi-Threaded Programming

Anonymous's picture

Very well written. Simple yet precise.

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState