Press "Enter" to skip to content

Multi-threaded Programming with POSIX Threads (pthreads): Part 1

Modern computers are based on multiprocessor architectures. Each “core” can execute only one instruction at a time, but all the processing cores combined can execute multiple instructions simultaneously.

However, program code that’s written in the traditional way is synchronous; that is the instructions follow one another in a logical and sequential manner.  And it adds to that, the code needs to be executed in that specific order for the program to be useful to the end-user, otherwise the program would be chaotic and would produce unexpected results. Therefore, it’s not possible for the Operating System to simply split a program into equal parts at run-time and execute each part on a different processing core. It would not work.

In order for a program to be able to take advantage of the extra processing cores on a computer, we need to structure our code into independent executable units, such that each independent unit can be executed on a different core, without affecting the logic of the program. That’s where the concept of threads come into play.

What’s a Thread?

While a process can be thought of as an instance of a program, a thread is a segment of program code that can be executed independently and concurrently with respect to other parts of the program.

In layman’s terms, a thread is basically a “procedure” that can be executed independently, either concurrently or at a later stage as deemed appropriate by the scheduler. Therefore, multi-threading is a programming model that can be used to implement parallelism in software.

A process can contain multiple threads, all sharing the same address space and resources as the process they belong to. However, the following are specific to each thread:

  • Registers
  • Stack Pointer
  • Program Counter
  • Signal Mask
  • Scheduling priorities/policies
  • Other thread specific data

The Need for POSIX Threads (pthreads)

Due to human ingenuity and originality, computer manufacturers have written their own implementation of threads. Because these vendor-specific implementations were all different from each other, programs written using a particular implementation of threads would not work on another vendor’s machine. This made it tedious for programmers to port their code to another platform where a different implementation of threads was being used.

In order to solve this problem, a standardized API was needed so that multi-threaded code written for one platform would also work on another. On UNIX systems, this API is known as POSIX.1c, Threads extensions (IEEE Std 1003.1c-1995), a.k.a POSIX threads, or simply pthreads.

The pthread API

The pthread API is defined in the C programming language and is implemented via the pthread.h header file and libpthread library on POSIX-compliant UNIX-like systems. The pthread API contains several procedures and data types that are related to the creation and management of threads.

Spawning a Thread

Spawning is just a fancy name that refers to creating a thread. Sometimes, as programmers, we want to sound professional and sophisticated to others. Because, why not? So from now on, we shall always talk of “spawning a thread” instead of “creating a thread”. Nah, just kidding :P. It’s probably best to use both. This will give us more diversity in our language. Anyway, enough talk. Let’s see how to create/spawn an actual thread using the pthread API !

First, we need to include the pthread.h header file in our code to be able to use the pthead API. Then, we need a function that returns a void pointer and accepts a void pointer as the only argument. It’s important to declare the thread function as such.

So far, we have only defined our thread function, and specified what it should do. In this case, our thread simply displays the line “From Thread: Hello World!”. Now, we need to write the code that spawns the thread.

We need a variable of type pthread_t to store the ID of the thread to be created. Next, the function pthread_create() is used to spawn the thread. The prototype for pthread_create() is shown below.

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

*tid is a pointer to a variable of type pthread_t that stores the ID of the thread.

*attr is a pointer to a structure of type pthread_attr_t that specifies the attributes to be used when creating the thread. Setting this to NULL will create a thread with default attributes.

*(*start_routine) is the entry point of the thread function.

*arg is a void pointer to the argument to be passed to the thread function. POSIX threads can accept only one argument. Set this to NULL if there is no argument to be passed.

The code above will compile just fine. However, the program will not work properly. That’s because when the main thread returns from main(), it will cause all threads to terminate, even if they are still in execution. This is an undesirable behavior since we would want our threads to be executed completely before exiting the program. In order to achieve this, we need a mechanism that allows main() to check if some threads are still running, and to wait for them to be completed before exiting the main() function. Such a mechanism can be implemented as follows.

Instead of putting return 0; as the last statement in main(), we terminate the main() function with pthread_exit(NULL); .

When pthread_exit(NULL); is the last statement in main(), it will cause the main() function to wait until all other threads have been executed. This prevents the program from being terminated as long as other threads are still running, thereby enabling the child threads to execute without any disruption.

The above code can be compiled on Linux as follows:

gcc pthreads_hello.c -lpthread -o pthreads_hello

Passing a Simple Argument

In the previous example, we spawned a thread without any argument. Now we’ll see how to pass an argument to a thread. But first let’s check the prototype for pthread_create() again.

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

As we have seen earlier,

*arg is a void pointer to the argument to be passed to the thread function. POSIX threads can accept only one argument. Set this to NULL if there is no argument to be be passed.

This means instead of passing the actual argument, we need to pass a pointer of type void to the thread via the pthread_create() function.

To get a void pointer to the argument, we use the reference operator & to first get an integer pointer to the argument, and then cast it to a void pointer using (void *) . For example: (void *)&age , where age is an integer that we are passing to the thread.

Because we passed a void pointer to the thread, we can’t access the value of age directly from the thread. First, we need to cast the void pointer back into an integer pointer using (int *) , and then de-reference the integer pointer using the * operator to get the actual value of age. For example: int age = *(int *)arg; . A concrete example is shown below.

Just like before, the code can be compiled as follows:

gcc pthreads_arg.c -lpthread -o pthreads_arg

Passing Complex Arguments

More often than not, we need to pass multi-dimensional arrays or more than one argument to a thread. However, the pthreads API allows us to create threads having only one argument. In order to circumvent this “limitation”, we should create a structure containing the arguments and pass it to the thread. Such a structure can elegantly be defined as follows:

The code above defines a structure with 3 elements: name, surname, age. Each of these element is an array.

The typedef  keyword at the beginning of the structure definition allows us to refer to the structure as a custom data type. In this case, we can refer to the structure as if it’s of the type args_displayInfo instead of struct _args_displayInfo , making it easier for us to work with it.

name and surname are 2D arrays of type char . They can each store 3 strings of 21 characters, including the null terminator.

age is a 1D array that can store 3 integers.

After having defined the composition of the structure, we can declare a structure of type args_displayInfo called params and assign values to its elements as follows:

We can now pass the params structure as an argument to the thread. Here is the complete code for convenience:

Have Fun, Be Thread-Safe

Having fun with threads so far ? Well, I am :P. Until now, we have spawned only 1 thread with several parameters by bundling the parameters into a structure and passing a pointer to the structure as an argument to the thread.

Suppose we now have 2 child threads:

Notice what we did here ? We have declared only 1 instance of the argument structure, and have passed the same instance to 2 different threads. Doing something like this is totally legal, but unsafe. The code above is said to not be thread-safe.

The reason why such a practice is not recommended is because both threads are sharing the same pointer to the argument. Therefore, each thread is free to modify the contents of the argument at its whims. This can lead to scenarios where the integrity of the argument cannot be guaranteed since either thread can change its value. In order to make the code thread-safe, a separate copy of the argument should be created and passed to each thread as shown below:

The code above is now thread-safe since each thread can modify the argument being passed to it without affecting the argument of other threads.

Returning a Value from a Thread

[Image courtesy of Livermore Computing Center]

All functions can return a value and threads are no exception. With the pthreads API, returning a value from a thread is achieved by using two functions:  pthread_join()  and  pthread_exit() . The prototype for each function is given below:

pthread_join()

int pthread_join(pthread_t tid, void **retval);

tid is a variable of type pthread_t  that contains the ID of the thread from which we want to get a return value.

**retval is a pointer to a void pointer to the return value.

pthread_exit()

void pthread_exit(void *retval);

*retval is a void pointer to the value to be returned from the thread.

Suppose we have a thread function calcAdd that adds 2 numbers specified in its structure argument and returns the sum of the 2 numbers.

Since we need to return a void pointer to the value to be returned, we first declare an integer pointer and allocates memory at that address using malloc() as follows: int *sum = (int *)malloc(sizeof(int)); .

Note: The header file stdlib.h needs to be included in order to use the malloc() function.

Next, we perform the addition by accessing the elements num1 and num2 from the argument structure, and store the results at the address pointed to by the integer pointer sum as follows: *sum = params.num1 + params.num2;

Finally, we cast the integer pointer sum to a void pointer so that the pthread_exit()  function can return the void pointer to the return value back to main().

Below is an extract of the main() function that waits for a return value from the thread function calcAdd.

Before spawning the thread, we have to declare a void pointer to the value that will be returned from the thread function calcAdd. Then, we create the thread normally using pthread_create() .

Next, pthread_join()  is used to make main() wait until the thread function calcAdd returns. The void pointer valPtr now points to the return value of the thread function calcAdd.

Finally, in order to get the actual return value, we simply cast the void pointer valPtr back into an integer pointer using (int *) , and then de-reference the integer pointer using the * operator as follows: *(int *)valPtr .

The complete code is listed below:

Conclusion

The pthreads API makes it super easy to create and manage threads. We have seen how to spawn threads using the pthread_create()  function, how to pass several parameters to a thread by bundling them into a structure, and how to be thread-safe.

Moreover, we have learned how to return a value from a thread by using pthread_exit() to return a void pointer to the return value, and by using pthread_join()  in the main() function to get the void pointer to the return value, after which we can de-reference it to get the actual return value.

This concludes part 1 of this tutorial. I hope you enjoyed reading this article as much as I enjoyed writing it. In part 2, we’re going to delve deeper into the pthreads API and have more fun with threads. Stay tuned 🙂

Sharing is caring
  •  
    13
    Shares
  • 13
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •