«  6.3. Race Conditions and Critical Sections   ::   Contents   ::   6.5. Thread Arguments and Return Values  »

6.4. POSIX Thread Library

In this section, we’ll return to the code from Code Listing 6.1 and replace the comments with functions from the POSIX thread library (pthreads). Pthread implementations are available for a wide variety of platforms, including Linux, macOS, and Windows. After considering the basic techniques to get threads started, we’ll look at how to pass arguments to and return data from the thread.

6.4.1. Creating and Joining Threads

Three functions define the core functionality for creating and managing threads. The pthread_create() function will create and start a new thread inside a process. The start_routine parameter specifies the name of the function to use as the thread’s entry point, just as main() serves as the main thread’s entry point. The pthread_exit() is used to exit the current thread and optionally return a value. Finally, the pthread_join() function is the thread equivalent of the wait() function for processes. That is, calling pthread_join() on a child thread will cause the current (parent) thread to wait until the child finishes and calls pthread_exit().

Decorative C library image

C library functions – <pthread.h>


int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
Create a new thread starting with at the start_routine function.
void pthread_exit (void *value_ptr);
Exit from the current thread.
int pthread_join (pthread_t thread, void **value_ptr);
Join a specified thread.

The arg argument to the pthread_create() function gets passed verbatim as the argument to the start_routine() function. That is, when the thread is created, it begins executing as if the programmer had called start_routine (arg); in their code. The only thing that is different is that the function will begin executing in a different thread.

The prototype for the pthread_create() function can be rather difficult to read if you are not completely comfortable with function pointers. Specifically, this prototype declares that the third parameter must be a pointer to a function that adheres to the following type declaration:

void *start_routine (void *args);

This prototype requirement simplifies the interface specification for thread creation. The trade-off is that passing parameters becomes more complicated. The following program illustrates how to create a child thread and wait for it to finish.

 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
/* Code Listing 6.2:
   A POSIX thread version of "Hello world"
 */

#include <stdio.h>
#include <pthread.h>
#include <assert.h>

/* Function that will run in the child thread */
void *
start_thread (void *args)
{
  printf ("Hello from child\n");
  pthread_exit (NULL);
}

int
main (int argc, char **argv)
{
  pthread_t child_thread;

  /* Create a child thread running start_thread() */
  assert (pthread_create (&child_thread, NULL, start_thread, NULL) 
          == 0);

  /* Wait for the child to finish, then exit */
  pthread_join (child_thread, NULL);
  pthread_exit (NULL);
}

Code Listing 6.2 illustrates a subtle point that is not immediately obvious to novices: The ``main()`` function defines a thread. Specifically, every program begins with a single thread of execution with main() as its entry point. Once the pthread library creates an additional thread, it is important that main() should be treated as a thread. In the simplest terms, this perspective implies that main() needs to end with pthread_exit() instead of the standard return statement. Using pthread_exit() ensures that all threads are managed and run correctly.

The main thread uses pthread_join() function to wait for the thread running start_thread() to call pthread_exit(). The standard thread terminology uses the term joining to mean waiting on a thread to complete. When a thread exits, any resources associated with it (such as its run-time stack) remain allocated until the thread is joined. As such, the pthread_join() and pthread_exit() combination can be used to return a value from the exiting thread to its parent. In this example, nothing is returned, so both of these functions take NULL as an argument.

Creating threads with pthread_create() happens asynchronously. That is, pthread_create() requests the allocation of resources for a new thread and returns 0 if the request is successful. The new thread may or may not begin running by the time ``pthread_create()`` returns. In fact, the new thread may have already run to completion by the time pthread_create() returns! The timing for running both the new and existing threads are determined by the system (the pthread library and the kernel). From the programmer’s perspective, this choice is nondeterministic.

Decorative bug warning

Bug Warning


There are a couple of common mistakes with pthread function parameters. With pthread_create(), the first parameter must point to a pthread_t instance. It is common to (incorrectly) declare the variable as a pthread_t*. Instead, the variable should be a pthread_t and the address of it should be passed as shown above. Similarly, the first parameter for pthread_join() must be a pthread_t instance, not a pointer. Next, the third parameter to pthread_create() must be the name of the function and must not include (). The parentheses would indicate that the function should be called (before creating the thread) and the function’s return value specifies the address to start at in the new thread. The following code sample illustrates these bugs.

1
2
3
4
5
6
/* WRONG! thread should not be a pointer in this example */
pthread_t *thread;
/* WRONG! thread is invalid pointer and start should not have () */
pthread_create (thread, NULL, start (), NULL);
/* WRONG! pthread_join() doesn't take pointer as first parameter */
pthread_join (thread, NULL); 

6.4.2. Attached and Detached Threads

In some scenarios, a thread will be created as detached instead of joinable. When a detached thread exits, its resources are immediately reclaimed for more efficient reuse. However, once a thread is detached, it can never be joined. This means that no other thread can wait on it to finish. According to the pthread specification, all threads are supposed to be created as joinable by default. The pthread_attr_setdetachstate() function can be used to change the attr argument to PTHREAD_CREATE_DETACHED to override the default.

1
2
3
4
5
6
7
pthread_attr_t attr;

/* Get the default set of attributes */
pthread_attr_init(&attr);

/* Mark the thread as detached */
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

Alternatively, the pthread_detach() function can be used to detach a running thread instance. A thread can detach itself by using its own pthread_t identifier as an argument to pthread_detach().

pthread_detach (pthread_self ());
Decorative C library image

C library functions – <pthread.h>


int pthread_detach (pthread_t thread);
Detaches the specified thread.
pthread_t pthread_self (void);
Returns the current thread’s pthread_t identifier.
«  6.3. Race Conditions and Critical Sections   ::   Contents   ::   6.5. Thread Arguments and Return Values  »

Contact Us License