Applying C - Pthreads
Written by Harry Fairhead   
Monday, 25 September 2023
Article Index
Applying C - Pthreads
Pthreads
Joinable And Detached Threads
Thread Local

Pthreads

Pthreads is standard on all POSIX systems, including Linux. To use it we need to include the pthreads library:

#include <pthread.h>

and also specify the name of the library that you want the linker to add to your program. To do this right click on the project and select properties. Select Build,Linker in the dialog box that appears, click the three dots in the Libraries section, click Add Library and specify pthread.

The key function for thread creation is:

int pthread_create(&pthread_t,options,&function,&param);

The first parameter is used to return the id of the thread. The second parameter can be set to NULL unless you want to modify the way the thread is created. The third parameter is a pointer to the function to be executed by the new thread and the final parameter is a pointer to a parameter to pass to the function. The function returns 0 if it worked and a negative value otherwise.

Let’s create a minimal thread creation program.

First we need a function to pass to the thread:

void *hello(void *p){
    printf("Hello Thread World");
}

Notice that the function accepts a void pointer and returns a void pointer. This means that the input parameter and the return result can be anything you care to use. However, you also need to keep in mind that returning a pointer to a dynamic object that the function created will fail as the object will be destroyed when the function terminates.

The main program can be as simple as:

int main(int argc, char** argv) {
    pthread_t pthread;
    int param=0;
    int id=pthread_create(&pthread,NULL,hello,&param);  
    return (EXIT_SUCCESS);
}

However, if you run this program you won’t see the message printed. The reason is that when the main program terminates all of its threads are also terminated and so the new thread never gets a chance to print anything. You might think that making the main program pause long enough would be enough, but because printf is writing to a buffer you still see nothing. To see the message you have to keep the main program running long enough for the thread to do its work and you need to fflush the buffer.

The complete program is:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void * hello(void *p){
    printf("Hello Thread World");
}
int main(int argc, char** argv) {
    pthread_t pthread;
    int param=0;
    int id=pthread_create(&pthread,NULL,hello,&param);    
    for(;;){fflush(NULL);};
    return (EXIT_SUCCESS);
}

Although we have just allowed the thread function to terminate there is an explicit exit function:

pthread_exit(void *retval);

and this also sets the pointer to the return value. Compare this to exit, which terminates the entire process, i.e. all of the threads. As explained later, the return value is accessible to threads that have joined the thread.

It is important to realize that all of the threads are on an equal footing. There is no master thread and the thread that was used to start the program is just another thread. Anything you can do in the main thread you can do in a thread you have created, including creating other threads, and vice versa. That is, although we tend to think about the main or default thread as special, it isn’t.

For example, when you call pthread_exit in the main program then the main thread will end but the process will continue to run until all of the threads have exited and the process has ended.

This means that our simple example could have been written:

int main(int argc, char** argv) {
    pthread_t pthread;
    int param=0;
    int id=pthread_create(&pthread,NULL,hello,&param);    
    pthread_exit(NULL);
}

The Thread Attributes Object

Much of the time a default thread will do, and you can pass NULL as the second argument to create. However, you do need to be able to customize the threads you create. To do this you have to pass a thread attributes object which is essentially a struct that you have to set using special functions.

You can create a thread attributes object using:

int pthread_attr_init(*attr);

and you can destroy it using:

int pthread_attr_destroy(*attr);

where attr is a pthread_attr_t.

So for example:

pthread_attr_t attrObj:
pthread_attr_init(&attrObj);

creates a thread attribute object set to the defaults. In this case:

int id=pthread_create(&pthread,&attrObj,hello,&param);

has the same effect as:

int id=pthread_create(&pthread,NULL,hello,&param);

To set values different from the defaults you have to call set functions and, to discover what the attribute is, there are corresponding get functions.

For example, to get the current size of the stack that will be used for threads created using this thread attribute object:

int pthread_attr_getstacksize(*attr, *stacksize);

The stack size will be stored in stacksize and to set it use:

int pthread_attr_setstacksize(*attr,stacksize);

Once you have finished using the thread attribute object you should destroy it.

This use of an abstract “object” to store attributes or state is something you will encounter again in other parts of Pthreads.

 

 



Last Updated ( Tuesday, 26 September 2023 )