C++ Multithreading: thread, mutex and async

C++11 added threads to the standard library. Include <thread>. On Linux, compile with -pthread.

#include <iostream>
#include <thread>

void work(int id) {
    std::cout << "worker " << id << " done\n";
}

int main() {
    std::thread t1(work, 1);
    t1.join();                                  // wait for the thread
    std::thread t2([] { std::cout << "lambda thread done\n"; });
    t2.join();
    std::cout << "main ends\n";
}
Output

worker 1 done
lambda thread done
main ends

A std::thread must be joined (or detached) before it is destroyed. If not, the program ends with std::terminate. In C++20 use std::jthread, which joins by itself.

Race condition

#include <iostream>
#include <thread>

int counter = 0;

int main() {
    auto job = [] { for (int i = 0; i < 100000; i++) counter++; };
    std::thread a(job), b(job);
    a.join(); b.join();
    std::cout << "Expected 200000, result is correct: " << (counter == 200000) << '\n';
}
Output

Expected 200000, result is correct: 0

Two threads changed the same variable without a lock. This is undefined behavior, and on a normal computer the result is usually wrong and changes in every run.

Fix with mutex

#include <iostream>
#include <mutex>
#include <thread>

int counter = 0;
std::mutex m;

int main() {
    auto job = [] {
        for (int i = 0; i < 100000; i++) {
            std::lock_guard<std::mutex> lock(m);        // locks here, unlocks at the end of the block
            counter++;
        }
    };
    std::thread a(job), b(job);
    a.join(); b.join();
    std::cout << counter << '\n';
}
Output

200000

Atomic

#include <atomic>
#include <iostream>
#include <thread>

std::atomic<int> counter{0};

int main() {
    auto job = [] { for (int i = 0; i < 100000; i++) counter++; };
    std::thread a(job), b(job);
    a.join(); b.join();
    std::cout << counter << '\n';
}
Output

200000

async and future

#include <future>
#include <iostream>
#include <numeric>
#include <vector>

int main() {
    std::vector<int> v(1000);
    std::iota(v.begin(), v.end(), 1);                    // 1 to 1000

    auto first_half = std::async(std::launch::async, [&] { return std::accumulate(v.begin(), v.begin() + 500, 0); });
    int second_half = std::accumulate(v.begin() + 500, v.end(), 0);
    std::cout << first_half.get() + second_half << '\n';   // get() waits for the result
}
Output

500500

Good practice

  • Prefer higher level tools (std::async, thread pools, parallel algorithms) to raw threads.
  • Always use lock_guard or scoped_lock, never lock and unlock by hand.
  • Use the same order of locks everywhere to avoid a deadlock.
Note: Use a tool like ThreadSanitizer (-fsanitize=thread) to find data races. Next: C++ Recursion.

Congratulations! Chapter Finished. Learn more about the similar topics:
Exercises & Assignments
No Content Found.
Interview Questions & Answers
No Content Found.