Introduction to threads with C++11
By Lucas van Dijk profile image Lucas van Dijk
5 min read

Introduction to threads with C++11

The free lunch is over. The time when our complex algorithm ran extremely slow on a computer but ran extremely fast a few years later because the processor speeds exploded is gone. The trend with current processors is to add more cores rather than increase the clock frequency.

As a programmer, you should be aware of this. Of course, processors will always perform a bit better each year, but the growth in performance is slowing down. Many programs can benefit the most by using multiple threads because of today’s multicore processors.

In this article, I’ll briefly explain what a thread is and how you can create them with the new threading library in C++11. I’m planning to write multiple articles about this topic, each going more in-depth.

This is part of a series of articles about multithreading with C++11, the other parts are listed below:

What is a thread?

On most general-purpose computers, we run many processes alongside each other. But let us ask the question, what is a process? Well, a simple definition is a running program. But what is a program, then? Another simple definition is a list of instructions the processor needs to execute.

Assume we have one single-core processor. How can all these programs run beside each other? After all, a single-core processor can perform only one instruction at a time. Well, that’s handled by the operating system. To share the processor with all these processes, it gives one process a little bit of time to perform some instructions, then goes to another process which gets a little bit of time, and so on. Some processes have higher priority than others and will get more processor time. This is called scheduling and is a subject on itself, and I won’t cover it much more in this article.

We can have threads within processes. It’s based on the same principle described above: on a single-core processor, each thread is given some processor time.

If we have a non-threading program, we start at the main() function, and the program is finished when we reach the end of the main() function. Fun fact: When you run this program, the operating system actually creates a new thread to run the main() function in, which we call the ‘main thread’ (creative name, isn’t it?).

You can create new threads from the main thread, which should run simultaneously with the main thread. This means that besides the ‘codepath’ main() till the end of main() we now also have another codepath, from the entry point of the new thread till the end of the thread. The thread's entry point is often the start of a function other than main(), and the end of the thread means the end of that function. But remember, on a single-core processor, it’s not really simultaneous; it shares the processor with the other threads.

We discussed single-core processors in the above text, but what about today’s multicore processors? Well, this means we can do multiple things at the same time. If your processor has two cores, then two threads can run simultaneously. If your processor has N cores, then your processor can run N threads at the same time. And this is why today multithreading is so important: we can actually do things in parallel.

Difference between processes and threads

Threads and processes both have the same purpose: running specific tasks simultaneously. Why do they both exist? Well, there are some differences, summarized below.

Properties of a process

  • The stack of a process is safe.
  • Each process has its own memory, which other programs can’t alter. (There are probably ways to do it, but in normal circumstances, it can’t be done.)
    • Because each process has its own memory, the memory is safe, but inter-process communication is slow
  • Switching from one process to another is heavy: processor-cache flush, memory management unit TLB flush.

Properties of a thread

  • The stack of a thread is safe
  • Each thread shares the same memory within the same process
    • Shared memory is unsafe, but inter-process communication is fast
  • Switching from one thread to another is fast (no flushes)

Defining threads with C++11

Fine, fine, let’s start coding. C++11's new threading library is really nice, and makes creating a new thread easy. Consider the example below; we’ll walk through the code afterward.

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <vector>

using std::string;
using std::thread;
using std::vector;
using std::cout;
using std::endl;

/**
 * Prints the given string `num_times` times, with `time_between` miliseconds
 * between each print.
 */ 
void printer(string text, int num_times, int time_between)
{
	for(int i = 0; i < num_times; i++) 
	{
		cout << text << endl;

		// Wait a moment before next print
		std::chrono::milliseconds duration(time_between);
		std::this_thread::sleep_for(duration);
	}
}

int main()
{
	// Create the threads which will each print something
	vector<thread> printers;

	printers.push_back(thread(printer, "Hello", 30, 10));
	printers.push_back(thread(printer, "World", 40, 15));
	printers.push_back(thread(printer, "Parallel World", 40, 20));

	for(auto &p : printers)
	{
		p.join();
	}
	
	return 0;
}

Of course, we first include some library files. The chrono and thread libraries are the most important here. Both are new in C++11. The chrono library provides some nice timing capabilities, and the thread library the classes for creating threads.

Then, we define a new function called printer. This is a straightforward function that just prints the given text a number of times, with a given time in between. You can see that the chrono library provides a nice and clean way to define the time between each print.

The main function is a bit more interesting; here, we create the other threads. C++11 provides a class thread. As you can see, we create three objects of this class, and put them all in a vector.

The thread constructor accepts a function as the first argument, and the rest of the arguments will be passed to the given function.

Because the vector implements the iterator API, we can use the new C++11 range based for syntax, and on each thread object we call the method join. This ensures the calling thread will now wait for the joined thread to exit before it finishes itself (in this case, the main thread, and thus the whole program). In our case, each printer thread must exit before the main thread finishes.

Running the program

When we compile and run this program, we get the following output:

HelloWorld

Parallel World
Hello
World
Parallel World
Hello
World
Hello
Parallel World
Hello
World
Hello
Parallel World
World
Hello
Hello
World
Parallel World
Hello
World
Hello
Parallel World
World
Parallel World
World
World
Parallel World
Parallel World
Parallel World
Parallel World
Parallel World
Parallel World
Parallel World
Parallel World

So yeah, we can see that our printer threads run beside each other, and not sequential.

There’s one thing to note here, although we have the following code:

cout << text << endl;

In the first line of the above output, the ‘Hello’ and ‘World’ are not on seperate lines. Something is not entirely right, but the why and how to fix it will be covered in my next article.

By Lucas van Dijk profile image Lucas van Dijk
Updated on
Tutorial Software Engineering