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.
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()
.
C library functions – <pthread.h>
int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
start_routine
function.void pthread_exit (void *value_ptr);
int pthread_join (pthread_t thread, void **value_ptr);
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.
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);
|
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 ());