DEV Community

Hoàng Văn Khoa
Hoàng Văn Khoa

Posted on

Introduction to multithreading in C/C++

This is the first in a series of blog posts about multithreading in C/C++. The series contains not only my notes but also some bits of advice I made to myself. If you find them helpful, that is so great. If you find incorrect information, please let me know.
I assumed you have some basic understanding of the thread of execution in general and some experience in C/C++ programming.

Hello Multithreading World

The Modern C++ Standards (C++11 and later) bring many exciting features into the C++ language and standard library that make coding in C++ easier, more productive, and more secure. One of those features is the C++ Standard Library's built-in support for multithreading. Before C++11, developers have to write C-style code using platform-specific C APIs to enable multithreading in their code.

Mentioning multithread C APIs, there are supports for multithreading in C. But they are not a part of the C Standard Library. I can name two here: Win32 API and POSIX.

While Win32 API is Windows-specific, POSIX is an industry standard that many systems implement. For example, macOS is a POSIX-certified system. Linux, however, isn't a POSIX-certified system. But it can be qualified as mostly POSIX-compliant. In automotive, a standard that requires its implementations must comply with POSIX is the AutoSAR Adaptive Platform.

Currently, as an automotive software engineer, I have two options to enable multithreading in my code:

  • C code and POSIX Threads (commonly known as Pthreads)
  • C++ code and C++ Thread

I can always write C-style code in C++ programs, but I will avoid that style of programming as much as possible. Mixing C-style code in a C++ program makes the code harder to understand and debug.

The next sections will show an overview of multithreading in the two standards.

Pthreads

The support for multithreading of a POSIX system consists of 3 components:

  • The header file pthread.h is the declaration of the API.
  • The implementation of the API.
  • A C compiler and linker.

For example, macOS is a POSIX-certified system. The implementation for Pthread is lib_pthread. The macOS SDK, which developers can download for free, provides the header file pthread.h and clang, the compiler and linker. I'd recommend downloading Xcode, which has the SDK included.

Another example that can be mentioned is Linux, a mostly POSIX-compliant system. The implementation is the Native POSIX Threads Library (NPTL), which is a part of The GNU C Library (glibc). By obtaining GCC, you have an implementation, the header file, the compiler and the linker. Most Linux distros have the package gcc. Follow your package manager manual to install it. For example:

  • On Ubuntu, run sudo apt-get install gcc
  • On Fedora, run sudo dnf install -y gcc

Grab the tools you need for your system, and take a look a the following hello world example:

.

In which:

  • helloWorld(): The function which shall be executed in the new thread.
  • pthread_t: Thread identifier type, needed by most Pthreads APIs.
  • pthread_create(): Creates and launches a new thread of execution. It also gives us an identifier and then returns an exit code.
  • pthread_join(): The calling thread waits for the thread (specified by an identifier) to terminate.

To compile the example, you can use either clang or gcc. You may want to use the system default C Compiler by using the cc command line utility:

.

As you may notice, I use some compiler options:

  • -Wall -Wextra: The compiler produces more warnings about potential defects.
  • -Werror: The compiler treats all warnings as errors.

The combination of these options prevents you from making mistakes in the early stage. You may find them annoying sometimes. But paying attention to the warnings, thinking about their meaning, and thinking of how to avoid them will make you a better programmer.

You may also notice that I don't ignore the exit codes that pthread_create() and pthread_join() return. In the example, the compiled program will return 0 if the execution is successful, or an error code that POSIX defines in errno.h. Experience users can look up the error code in errno.h and find out what was wrong. That's very basic error handling. Remember, don't skip error handling. The more time you spend on writing error handling code, the less time you will have to spend on defect investigation. Defects on multithreaded code are very hard to find, so keep that in mind.

That's the hello world of Pthreads. Next, I will introduce the C++ Thread.

C++ Thread

Unlike the C Standard, modern C++ Standards (since C++11) specify built-in supports for multithreading in the C++ Standard Library. These supports include the classes for managing threads, protecting shared data, synchronizing between threads, and low-level atomic operations, which makes coding multithread easier and safer. I think the best part is now you can have a consistent C++ programming experience while working with threads, regardless of the runtime system.

Note that the C++ Standard is a technical specification. The actual implementation is done by compiler vendors, like GCC, LLVM, Microsoft C++, etc. On a POSIX system, C++ Thread is implemented using Pthreads. In this case, C++ Thread is a wrapper that wraps underlying POSIX APIs. This action comes with a cost, called the abstraction penalty. In most cases, developers are happy to pay this cost. But if the utmost in performance is what you're after, then consider writing your code in C, with the only support for multithreading being either platform-specific or Pthreads. Other than that, if you're programming in C++, I'd recommend C++ Thread.

You can start writing the hello world example with a C++ compiler that supports C++11 or later:

.

In which:

  • The class std::thread is declared in the header file thread.
  • The function which will be executed in the new thread is defined in the class BackgroundTask.
  • The new thread will be created and launched by the constructor of std::thread.
  • The std::thread::join() member function causes the calling thread waits for myThread to terminate.
  • Instead of C-style error code handling, C++ provides exception handling mechanism.

Both the constructor and the member function join() of the class std::thread can throw std::system_error exception. Although C++ compilers don't require catching every thrown exception, I think it's a good practice that you understand every exception a function can throw, and handle them properly. In the example, I just print out the information about the exception that std::system_error::what() provides, and returns -1, indicating a failure.

To compile the example, refer to the following command line for your compiler (g++ or clang++):

.

The meaning of the compiler options is the same as gcc and clang.

So, that's hello world to Pthreads and C++ Thread. In the next post, I will introduce to you how to launch a thread, using Pthreads and C++ Thread.

Top comments (0)