A Brief Intro to Shared-memory Programming with POSIX Threads

Jithmi Shashirangana
6 min readSep 2, 2020

--

Of course, before we start talking about POSIX threads programming, let’s quickly refresh some basic concepts and design considerations in shared-memory programming. So, this article is gonna be ideal for those who are new to parallel programming with POSIX threads or sometimes referred to as Pthreads.

Process Vs Thread

A process is any program in execution that allows you to perform the appropriate actions specified in a program. While a thread is a path of execution within a process which is also considered as a lightweight process and it uses the process resources. Therefore, a process can have multiple threads, all executing at the same time. However, the main difference is that threads within the same process run in shared memory space, while processes run in separate memory spaces. Therefore, in shared memory multiprocessor architectures, threads can be used to implement parallelism. Multiple threads share information like data, code, OS resources such as files, etc. Similar to a process, a thread has its own program counter, a stack, and a set of registers. Since each thread has its independent resources for process execution, multiple processes can be executed parallelly by increasing the number of threads.

Now, Let’s get started with POSIX programming…..

POSIX Threads or Pthreads

POSIX threads or more often called Pthreads specifies an application programming interface (API) for multithreaded programming based on UNIX. Unlike C or Java, Pthreads is not a programming language and rather it is a library that can be linked with C programs. Except for Pthreads, there exist some other specifications for multithreaded programming such as Java threads, Windows threads, Solaris threads, etc. However, in this article let’s look at Pthreads and once we are done, it won’t be difficult to learn how to program with another thread API that I mentioned earlier.

Why Pthreads?

  • Lightweight — Typically, the cost of creating and managing a process is higher than that of a thread, and therefore, threads can be created with less OS overhead. Also, threads require fewer system resources than processes.
  • Efficient Communication/ Data Exchange — when compared to using MPI (Message Passing Interface) libraries for on-node communication, using Pthreads may become a huge benefit over achieving a better performance. MPI libraries usually implement on-node task communication via shared memory, which involves at least one memory copy operation (process to process). Whereas, for Pthreads, there is no intermediate memory copy required because threads share the same address space within a single process. Therefore, there is no data transfer and can be as efficient as simply passing a pointer.

The Pthreads API

The Pthreads API can be informally divided into four major groups.

  1. Thread Management: handles creating, terminating, and joining threads, etc.
  2. Mutexes: deals with synchronization, called a “mutex”, an abbreviation of mutual exclusion. Mutex functions provide for creating, destroying, locking, and unlocking mutexes.
  3. Condition variables: address communication among the threads that share a mutex. This specifies functions to create, destroy, wait, and signal based upon specified variable values.
  4. Synchronization: manage read/write locks and barriers.

In this article, let’s learn how to create and terminate threads using the Pthreads API.

Execution

The Pthreads programs are compiled similar to an ordinary C program and additionally, we need to link the Pthreads library. If you are using a GNU C compiler, the compiler command for the Pthreads program is as follows.

gcc -g -Wall -o pth_name pth_name.c -lpthread

Thread Management

Creating and Terminating Threads

Initially, your main() program comprises a single, default thread and still, all the other threads must be explicitly created by the programmer. So, to create a new thread we can use the pthread_create function in the Pthreads API. The syntax for the pthread_create is,

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

pthread_create function has four arguments:

  • thread: An opaque, unique identifier for the new thread returned by the subroutine.
  • attr: An opaque attribute object that may be used to set thread attributes. You can specify a thread attributes object or NULL for the default values.
  • start_routine: the C routine that the thread will execute once it is created.
  • arg: a single argument that may be passed to start_routine. It must be passed by reference as a pointer cast of type void. NULL may be used if no argument is to be passed.

However, there are several ways in which a thread may be terminated. Normally, a thread terminates once its work is done or if it makes a call to the pthreads_exit subroutine. Also, a thread is canceled by another thread via the pthread_cancel routine. Except for these, the entire process can be terminated by making a call to either the exec() or exit(). However, a problem occurs if the main() finishes before the threads it spawned and if you don’t call pthread_exit() explicitly. In this scenario, it will terminate all the threads it created as the main() is done and no longer exists. But if you call pthread_exit() explicitly, then the main() will be blocked and kept alive o support the threads it created until they are done.

Example: Pthread Creation and Termination

Now let’s dive into a simple example code that creates 5 threads with the pthread_create() subroutine and terminates with a call to pthread_exit() routine.

#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *PrintHello (void *threadid)
{
long tid;
tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc;
long t;
for(t=0; t<NUM_THREADS; t++){
printf("In main: creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
/* Last thing that main() should do */
pthread_exit(NULL);
}

Now, let’s take a closer look at the source code in the example. As in any other C program, this program includes some familiar header files such as stdio.h. However, here we also need to include the pthread.h, the Pthread header file which declares the various Pthreads functions, constants, and types. Next, you have to define a global variable for the number of threads and here it is assigned to 5. Then we have used the two functions pthread_create() and pthread_exit() that we discussed earlier. First, we have allocated storage for one pthread_t object for each thread. The first argument of the pthread_create() is a pointer to the appropriate pthread_t object. These pthread_t objects are examples of opaque objects and therefore the actual data that they store is system-specific, and their data members aren’t directly accessible to user code. However, the Pthreads standard guarantees that a pthread_t object does store enough information to uniquely identify the thread with which it’s associated. We won’t be using the second argument, so we just pass the argument NULL in our function call. The third argument is the function that the thread is to run, and the last argument is a pointer to the argument that should be passed to the function start routine. So the final output will be something like this below.

In main: creating thread 0
In main: creating thread 1
Hello World! It's me, thread #0!
In main: creating thread 2
Hello World! It's me, thread #1!
Hello World! It's me, thread #2!
In main: creating thread 3
In main: creating thread 4
Hello World! It's me, thread #3!
Hello World! It's me, thread #4!

Conclusion

In this article, I just briefly described only one major part in Pthreads API which is the Thread Management. There, I showed how to create and terminate threads using a simple hello world example. However, there is a lot more to play with Pthreads API and as for a start I suggest playing with this example and see how concurrency takes action.

References

[1]”Introduction to Parallel Computing”, Computing.llnl.gov, 2020. [Online]. Available: https://computing.llnl.gov/tutorials/parallel_comp/. [Accessed: 02- Sep- 2020].

--

--

Jithmi Shashirangana

Senior Software Engineer | AWS Certified Developer | Java Backend Developer | Passionate writer exploring the fascinating world of AI.