Concurrency in Modern C++

Photo by Tim Swaan on Unsplash

Concurrency in Modern C++

Ever wondered how to spawn threads in c++ ? How to create promises/ async functions in c++ ?

ยท

3 min read

Concurrency in modern C++ refers to the ability to perform multiple tasks or operations simultaneously.

Multiple things can happen at once but the order matters and sometimes tasks have to wait on shared resources

Concurrency is slightly different from parallelism, in parallelism, everything happens at once simultaneously. For example, an orchestra is an example of concurrency not parallelism

It allows you to make the most of today's multi-core processors to improve the efficiency and responsiveness of your software. In C++, you can achieve concurrency through various mechanisms:

Threads

  1. Threads: C++ provides a standard thread library (std::thread) that allows you to create and manage concurrent threads of execution.

    • A thread allows us to execute two control flows at the same time

    • Here the order is enforced as join() blocks the execution of the thread until the thread identified by *this finishes the execution

      Launching multiple Threads:

    • Now for the above code, all 10 thread IDs will be different and the order of outputs will be jumbled up due to Preemptive scheduling

    • In C++ 20 another type of thread known as jthread is introduced which is by default joined to the parent thread

Mutexes and Locks

  1. Mutexes and Locks: You can use mutexes (std::mutex) and locks (e.g., std::unique_lock) to protect shared resources from data races.

    • Mutex is also called a binary semaphore

      Avoiding Deadlocks (caused when somehow unlock logic execution doesn't work)

Atomic Operations

  1. Atomic Operations: C++ also provides atomic types and operations (e.g., std::atomic) to safely manipulate shared data in a multi-threaded environment.

    • As we are concerned with a single variable only for a single variable it better suited if we make its type an atomic

    • But we need to be very careful as only certain operations are overloaded to be performed on this atomic variable for example post-increment is overladed but + operation is NOT.

Async and Futures

  1. Async and Futures: You can use std::async and std::future to create asynchronous tasks and manage their results.

    • Allows us to launch a thread and not be blocked on that thread, but would eventually resolve from that thread

      • Using the above we can compute in another thread and then get results asynchronously

      • SAMPLE PROGRAM of executing background tasks via async and futures

        •   #include <iostream>
            #include<thread>
            #include<future>
            #include <chrono>
            using namespace std;
          
            bool bufferedFileLoader(int x) {
                size_t bytesLoaded =0;
                while (bytesLoaded<20000) {
                    cout<<"loading file in background... "<<bytesLoaded<<endl;
                    this_thread::sleep_for(chrono::milliseconds(1000));
                    bytesLoaded+=1000;
                }
                return true;
            }
          
            int main() {
                future<bool> backGroundThread=async(launch::async,bufferedFileLoader,5);
                // The first arg is policy which is has two values
                //async or deferred(compute result only when requested ie the .get())
                // We can also keep track of the status of this task
          
                future_status status;//enum with values ready,timeout,deferred;
          
                while (true) {
                    cout<<"main thread in foreground...."<<endl;
                    this_thread::sleep_for(chrono::milliseconds(1000));// frame rate
          
                    status=backGroundThread.wait_for(chrono::milliseconds(1));
                    // wait_for same as await in JS, arg is timeout
                    //this line blocks for max 1ms to check the status
          
                    if(status==future_status::ready){
                        break;
                    }
                }
                cout<<"Exiting ..."<<endl;
          
                return 0;
            }
          
ย