User Tools

Site Tools


Sidebar

os_cp:threads

This is an old revision of the document!


Threads

A process can run multiple threads – multiple flows of control.
In general, the threads share all resources (such as memory or open files).
Each thread uses a separate stack, and obviously has its own state of the CPU registers. A limited set of properties might also be specific to a thread (e.g., signal mask, CPU affinity).

Since 2012 the C and C++ languages include API for concurrency, including threads. However, it is very generic, so that it can be implemented in all operating systems.

POSIX C C++
create a new threadpthread_createthrd_createstd::thread constructor
join a threadpthread_jointhrd_joinstd::thread::join
detach a threadpthread_detachthrd_detachstd::thread::detach
exit from a threadpthread_exitthrd_exit
send signal to a specific threadpthread_kill

A process always starts with a single thread called the main thread.
When the main thread returns from main function, or when any thread calls exit function, all threads are forcefully terminated.

pthreads

The POSIX standard defines an API for threads called pthreads. While each operating system has its own implementation of threads, virtually any Unix-like system implements the POSIX Threads API.
Linux implementation of threads is called NPTL. Apart from the standard pthread functions, it offers several Linux-specific functions (which names end with _np to indicate non-portability).

To compile any program that uses pthreads, one had to add -pthread option upon invoking compiler and linker.
This changed since version 2.34 of glibc, that moved POSIX thread routines to standard C library (libc).

Creating a thread

To start a new thread, one must point to the function that will be executed by the thread.
The function must take a pointer to arbitrary data as an argument and return an pointer to arbitrary data1):

void * start_routine (void * argument){return some_pointer;
}

In C such function is of type void *(*)(void *), and a variable called func of this type should be declared as void *(*func)(void *).
A new thread will get an identifier of type pthread_t that can be used later on to refer to the thread.

The function starting a new thread has the following syntax:
Needs header:
pthread.h
int pthread_create(pthread_t *restrict threadIdentifier,
                   const pthread_attr_t *restrict attr,
                   void *(*start_routine)(void *),
                   void *restrict arg);
It will write the thread identifier to the variable pointed by threadIdentifier, start a new thread and run start_routine(arg) in it. If the attr argument is not NULL, thread attributes will be set before starting the thread.

An example of creating a thread and passing it some data

Caring for a thread

Just like a child process is remembered by the operating system until the parent process reaps it, a thread that has terminated is remembered by the operating system until another thread joins it. Alternatively, a thread can be explicitly detached so that it is immediately reaped upon termination and cannot be joined anymore.

By default, a thread starts in joinable state. One can create a set of attributes (using pthread_attr_init and pthread_attr_setdetachstate) that tells the OS to create a thread in the detached state.

To join a thread and collect its result one has to call the function:
Needs header: pthread.h int pthread_join(pthread_t thread, void **value_ptr);
Calling the pthread_join waits until the thread thread terminates and writes its return value to the void * pointer pointed by value_ptr. The value_ptr can be NULL, and then the return value is discarded.

An example code that creates 3 threads and joins them, collecting their result.

To detach a thread one has to call the function:
Needs header: pthread.h int pthread_detach(pthread_t thread);
The pthread_detach function returns without waiting. The value returned by the detached thread will be discarded.
A thread can detach itself (i.e. call pthread_detach(pthread_self());), but typically the thread that spawned it calls the detach.

An example code that creates a thread and detaches it.

Exercises

Exercise 1 In the main thread create a new thread, join it and then return from main.
In the newly created thread write Hello World to the standard output.

Exercise 2 Remove the code you wrote for the previous exercise the pthread_join and re-run the code. What has changed in the behaviour of the program?

Exercise 3 In the main thread create 10 new threads, passing to each of them an ordinal number. Then, join each thread and exit.
In the newly created threads write the number to the standard output.

Exercise 4 Add to the program from the previous exercise a global variable. In each thread read the variable and increment it. Output the obtained value together with the ordinal number, and return it from the thread entry routine. In the main thread, collect the returned numbers and display them.

Thread-specific data, thread local storage [extra]

In C, data items can have different storage duration and different linkage. Until C11, there existed only static, automatic or allocated storage durations. Roughly:
 • static = global variables and static variables defined inside functions,
 • automatic = items on stack, automatically allocated inside functions ('local variables'),
 • allocated = items on heap, explicitly allocated and freed.
None of these cover a case when one wants a static ("global") variable with a separate value for each thread. Such storage duration is called thread local and a coupe of ways to create thread-local items are presented in this section.

POSIX Thread-specific data

One can create keys (that may be understood as identifiers of variables) and then associate, for each thread separately, a value for a key. To this end the following functions can be used:
Need header:
pthread.h
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))
int pthread_setspecific(pthread_key_t key, const void *value)
void *pthread_getspecific(pthread_key_t key)

Each key is initially associated with a NULL for each thread.

An example code that creates a key, stores and retrieves the value from two threads.

C thread-local storage

Starting with C11, a new storage duration _Thread_local or thread_local2) is defined. Variables defined with this storage duration have unique value for each thread. Notice that the thread_local variables can have initial values (unlike in POSIX), but there is no way to provide a custom destructor (unlike in POSIX).

1) To specify a pointer without specifying the type of underlying data one has to use void *.
2) Until C23 thread_local was a macro defined in threads.h, since C23 it is a keyword with same meaning as the _Thread_local keyword.
os_cp/threads.1684281061.txt.gz · Last modified: 2023/05/17 01:51 (external edit)