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.

1

2

3

4

5

6

7

8

9

10

11

12

#include <stdio.h>

#include <pthread.h>

void*thread_function(void*arg)

{

printf("From Thread: Hello World! \n");

}

intmain()

{

}

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.

*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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#include <stdio.h>

#include <pthread.h>

void*thread_function(void*arg)

{

printf("Thread: Hello World! \n");

}

intmain()

{

pthread_t thread_id;

pthread_create(&thread_id,NULL,thread_function,NULL);

//Problem: How do we tell main() to wait for our thread

//to execute ?

return0;

}

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
return0; 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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

/*

* Project: Pthreads Tutorial

* File: pthreads_hello.c

* Date: September 9, 2018

* Author: Muzaffar Auhammud

* License: MIT License

*/

#include <stdio.h>

#include <pthread.h>

void*thread_function(void*arg);

intmain()

{

pthread_t thread_id;

pthread_create(&thread_id,NULL,thread_function,NULL);

printf("From main: Hello World !\n");

printf("Exiting: main\n");

//This should be the last statement in main()

pthread_exit(NULL);

}

void*thread_function(void*arg)

{

printf("From thread: Hello World !\n");

printf("Exiting: thread\n");

}

The above code can be compiled on Linux as follows:

gccpthreads_hello.c -lpthread -opthreads_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.

*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:
intage=*(int*)arg; . A concrete example is shown below.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

/*

* Project: Pthreads Tutorial

* File: pthreads_arg.c

* Date: September 9, 2018

* Author: Muzaffar Auhammud

* License: MIT License

*/

#include <stdio.h>

#include <pthread.h>

void*displayAge(void*arg);

intmain()

{

intage;

//Ask user to provide an age

printf("Enter age: ");

scanf("%d",&age);

//Spawn the displayAge thread with age as argument

pthread_t displayAge_id;

pthread_create(&displayAge_id,NULL,displayAge,(void*)&age);

//This should be the last statement in main()

pthread_exit(NULL);

}

void*displayAge(void*arg)

{

//Get the actual value of age

intage=*(int*)arg;

//Display the age

printf("Age entered: %d \n",age);

}

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

gccpthreads_arg.c -lpthread -opthreads_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:

1

2

3

4

5

6

typedefstruct_args_displayInfo

{

charname[3][21];

charsurname[3][21];

intage[3];

}args_displayInfo;

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//Create an instance of the arguments structure

args_displayInfo params;

//Read in values in the arguments structure

for(inti=0;i<3;i++)

{

//Ask user to provide a name

printf("Enter name: ");

scanf("%s",&params.name[i]);

//Ask user to provide a surname

printf("Enter surname: ");

scanf("%s",&params.surname[i]);

//Ask user to provide an age

printf("Enter age: ");

scanf("%d",&params.age[i]);

printf("\n");

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

/*

* Project: Pthreads Tutorial

* File: pthreads_arg_complex.c

* Date: September 9, 2018

* Author: Muzaffar Auhammud

* License: MIT License

*/

#include <stdio.h>

#include <pthread.h>

void*displayInfo(void*arg);

typedefstruct_args_displayInfo

{

charname[3][21];

charsurname[3][21];

intage[3];

}args_displayInfo;

intmain()

{

//Create an instance of the arguments structure

args_displayInfo params;

//Read in values in the arguments structure

for(inti=0;i<3;i++)

{

//Ask user to provide a name

printf("Enter name: ");

scanf("%s",&params.name[i]);

//Ask user to provide a surname

printf("Enter surname: ");

scanf("%s",&params.surname[i]);

//Ask user to provide an age

printf("Enter age: ");

scanf("%d",&params.age[i]);

printf("\n");

}

//Spawn the displayInfo thread with 'params' as argument

pthread_t displayInfo_id;

pthread_create(&displayInfo_id,NULL,displayInfo,(void*)&params);

//This should be the last statement in main()

pthread_exit(NULL);

}

void*displayInfo(void*arg)

{

//Get the actual arguments structure

args_displayInfo params=*(args_displayInfo*)arg;

//Display the values from the arguments structure

for(inti=0;i<3;i++)

{

printf("Full Name: %s %s\n",params.name[i],params.surname[i]);

printf("Age: %d \n",params.age[i]);

printf("\n");

}

}

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:

1

2

3

4

5

6

7

8

9

10

11

12

//Create an instance of the arguments structure

args_displayInfo params;

//Code to read in values in the arguments structure

//Spawn the displayInfo thread with 'params' as argument

pthread_t displayInfo_id;

pthread_create(&displayInfo_id,NULL,displayInfo,(void*)&params);

//Spawn the displayData thread with 'params' as argument

pthread_t displayData_id;

pthread_create(&displayData_id,NULL,displayInfo,(void*)&params);

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//Create 2 instances of the arguments structure

args_displayInfo params1;

args_displayInfo params2;

//Code to read in values in the params1 structure

//Code to read in values in the params2 structure

//Spawn the displayInfo thread with 'params1' as argument

pthread_t displayInfo_id;

pthread_create(&displayInfo_id,NULL,displayInfo,(void*)&params1);

//Spawn the displayData thread with 'params2' as argument

pthread_t displayData_id;

pthread_create(&displayData_id,NULL,displayInfo,(void*)&params2);

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()

intpthread_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()

voidpthread_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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

void*calcAdd(void*arg)

{

//Get the actual arguments structure

args_calcAdd params=*(args_calcAdd*)arg;

//Declare pointer to the return value

//and allocate memory at that address

int*sum=(int*)malloc(sizeof(int));

//Perform the addition and store the results

//at location pointed to by sum

*sum=params.num1+params.num2;

//Return void pointer to the value we want to be returned

pthread_exit((void*)sum);

}

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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//Declare a void pointer to the return value

void*valPtr;

//Spawn the calcAdd thread with 'params' as argument

pthread_t calcAdd_id;

pthread_create(&calcAdd_id,NULL,calcAdd,(void*)&params);

//Tell main() to wait for calcAdd to return a value

//The void pointer to the return value will be in valPtr

pthread_join(calcAdd_id,&valPtr);

//Cast the void pointer to an integer pointer

//and de-reference it to get actual value

printf("The sum is: %d \n",*(int*)valPtr);

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

/*

* Project: Pthreads Tutorial

* File: pthreads_return.c

* Date: September 9, 2018

* Author: Muzaffar Auhammud

* License: MIT License

*/

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

void*calcAdd(void*arg);

typedefstruct_args_calcAdd

{

intnum1;

intnum2;

}args_calcAdd;

intmain()

{

//Create an instance of the arguments structure

args_calcAdd params;

//Ask user to provide a number

printf("Enter a number: ");

scanf("%d",&params.num1);

//Ask user to provide another number

printf("Enter another number: ");

scanf("%d",&params.num2);

//Declare a void pointer to the return value

void*valPtr;

//Spawn the calcAdd thread with 'params' as argument

pthread_t calcAdd_id;

pthread_create(&calcAdd_id,NULL,calcAdd,(void*)&params);

//Tell main() to wait for calcAdd to return a value

//The void pointer to the return value will be in valPtr

pthread_join(calcAdd_id,&valPtr);

//Cast the void pointer to an integer pointer

//and de-reference it to get actual value

printf("The sum is: %d \n",*(int*)valPtr);

//This should be the last statement in main()

pthread_exit(NULL);

}

void*calcAdd(void*arg)

{

//Get the actual arguments structure

args_calcAdd params=*(args_calcAdd*)arg;

//Declare pointer to the return value

//and allocate memory at that address

int*sum=(int*)malloc(sizeof(int));

//Perform the addition and store the results

//at location pointed to by sum

*sum=params.num1+params.num2;

//Return void pointer to the value we want to be returned

pthread_exit((void*)sum);

}

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 🙂