What is Multithreading?

Multithreading is an ability of a platform (Operating System, Virtual Machine etc.) or application to create a process that consists of multiple threads of execution (threads). A thread of execution is the smallest sequence of programming instructions that can be managed independently by a scheduler. These threads can run parallel and it can increase efficiency of programs.

In Multicore and Multiprocessor systems multithreading means that different threads are executed at the same time on different cores or processors.

For single core systems multithreading divides the time between the threads. The operating system in turn sends a certain number of instructions from each thread to the processor. Threads are not executed simultaneously. Operating System only simulates their simultaneous execution. This feature of the operating system is called multithreading.

Multithreading is used when the parallel execution of some tasks leads to a more efficient use of resources of the system.

Built in support for multithreading was introduced in C++11. Header file thread.h provides functionality for creating multithreaded C++ programs.

How to create a thread?

First, you have to include thread header in your program:

#include <thread>

When you want to create a thread, you have to create an object of a thread class.

//this thread does not represent any thread of execution
thread t_empty;

As you can see, when default constructor of thread class is used, we do not pass any information to the thread. This means, that nothing is executed in this thread. We have to initialize a thread. It can be done in different ways.

Initializing thread with a function

When you create a thread, you can pass a pointer of a function to its constructor. Once thread is created, this function starts its work in a separate thread. Look on an example:

Try to compile and run this program. It compiles without any errors but you will get a runtime error:

As you can see, main thread creates new thread funcTest1 with a parameter threadFunc. Main thread does not wait for funcTest1 thread termination. It continues its work. The main thread finishes execution, but funcTest1 is still running. This causes error. All the threads must be terminated before main thread is terminated.

Join threads

Thread joining is done by using join() member function of a thread class:

void join();

This function returns only after all the threads are terminated. It means that the main thread will wait until child thread does not finish its execution:

Call join() for the thread, created in the previous example and run the program again:

//pass a function to thread
thread funcTest1(threadFunc);
//main is blocked until funcTest1 is not finished
funcTest1.join();

As you can see, now program is executed successfully.

Joinable and not Joinable threads

After join() returns, thread becomes not joinable. A joinable thread is a thread that represents a thread of execution which has not yet been joined.

A thread is not joinable when it is default constructed or is moved/assigned to another thread or join() or detach() member function is called.

Not joinable thread can be destroyed safely.

You can check if a thread is joinable by using joinable() member function:

bool joinable()

This function returns true if the thread is joinable and false otherwise. It’s better to check if the thread is joinable before join() function is called:

//pass a function to thread
thread funcTest1(threadFunc);
//check if thread is joinable
if (funcTest1.joinable())
{
//main is blocked until funcTest1 is not finished
funcTest1.join();
}

Detaching thread

As we mentioned above, thread becomes not joinable after detach() member function is called:

void detach()

This function detaches a thread from the parent thread. It allows parent and child threads to be executed independently from each other. After the call of detach() function, the threads are not synchronized in any way:

As you can see, this function takes three arguments. If you want to initialize a thread with this function, first you have to pass a pointer to this function, then pass the arguments to the function in the same order as they are in the parameter list of the function:

When you pass arguments to the member function of a class, you have to specify arguments in the same order as they are listed in the parameter list of the function. It is done after the second parameter of the thread constructor:

thread test2(&myFunctorParam::changeSign, &objParamPass, arr2, 5);

Thread ID

Every thread has its unique identifier. Class thread has public member function that returns the ID of the thread:

As you can see, there is a global vector vec of integer values. Two threads push and pop try to access this vector simultaneously: the first thread pushes an element to the vector and the second one tries to pop an element from the vector.

The access to the vector is not synchronized. Threads are accessing vector non-continuously. Because of simultaneous access to shared data many errors can appear.

Mutex

Class mutex is a synchronization primitive that is used to protect shared data from simultaneous access. A mutex can be locked and unlocked. Once a mutex is locked, current thread owns mutex until it is not unlocked. It means that no other thread can execute any instructions from the block of code surrounded by mutex until thread that owns mutex unlocks it. If you want to use mutex, you have to include mutex header in the program:

#include <mutex>

After this, you have to create a global variable of mutex type. It will be used to synchronize access to the shared data:

Once you want that a portion of program to be executed only by one thread in the same period, you have to “lock” it using mutex:

Operations of pushing and popping elements to the vector are locked using mutex. Therefore, if a thread enters a block of instructions and locks the mutex, no any thread can execute this code until mutex is unlocked. Try to execute this program again:

We can examine another example of mutex usage. Imagine the following situation:

“A lot of people run to a call-box to talk to their friend. The first person to catch the door-handle of the call box is the only one who is allowed to use the phone. He must keep holding on to the handle of the door as long as he uses the call box. Otherwise, someone else will catch hold of the handle, throw him out and talk to his friend. There is no queue system as in real life. When the person finishes his call, exits the call-box and leaves the door handle, the next person that gets hold of the door handle will be allowed to use the phone.”

In this case, you have to imagine a problem of simultaneous access to data in the following way:

A thread is a person. The mutex is the door handle. The lock is the person's hand. The resource is the phone.

Any thread which has to execute some lines of code which should not be executed by other threads at the same time (using the phone to talk to his friend), has to first acquire a lock on a mutex (clutching the door handle of the call-box). Only then, a thread will be able to run those lines of code (making the phone call).

Once the thread finishes executing that code, it should release the lock on the mutex so that another thread can acquire a lock on the mutex (other people being able to access the phone booth).