Skip to content

Last updated:2026-10-12

Getting Started with C++ Multithreading

All the code in this article can be found in the C++那些事 repository.

https://github.com/Light-City/CPlusPlusThings

1. Understanding threads and processes

A thread is the basic unit of scheduling; a process is the basic unit of resource allocation. You can think of a program as a process, and a process in turn contains multiple threads.

For example: the browser is a process, and each tab you open is a thread.

The simple differences between the two:

  • Address space and other resources: processes are independent of each other, while threads of the same process share them. Threads within one process are not visible to other processes.
  • Communication: processes use inter-process communication (IPC), while threads can communicate directly by reading and writing the process data segment (such as global variables) — with the aid of process synchronization and mutual-exclusion mechanisms to guarantee data consistency.
  • Scheduling and switching: a thread context switch is much faster than a process context switch.
  • In a multi-threaded OS, a process is not an executable entity.

IPC communication and thread communication will each get a new article later on.

2. Five ways to create threads

  • Function pointers
  • Lambda functions
  • Functors (function objects)
  • Non-static member functions
  • Static member functions

2.1 Function pointers

// 1.函数指针
void fun(int x) {
    while (x-- > 0) {
        cout << x << endl;
    }
}
// 调用
std::thread t1(fun, 10);
t1.join();

2.2 Lambda functions

// 注意:如果我们创建多线程 并不会保证哪一个先开始
int main() {
    // 2.Lambda函数
    auto fun = [](int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    };
//    std::1.thread t1(fun, 10);
    // 也可以写成下面:
    std::thread t1_1([](int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }, 11);
//    std::1.thread t2(fun, 10);
//    t1.join();
    t1_1.join();
//    t2.join();
    return 0;
}

2.3 Functors

// 3.functor (Funciton Object)
class Base {
public:
    void operator()(int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }
};
// 调用
thread t(Base(), 10);
t.join();

2.4 Non-static member functions

// 4.Non-static member function
class Base {
public:
    void fun(int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }
};
// 调用
thread t(&Base::fun,&b, 10);
t.join();

2.5 Static member functions

// 4.Non-static member function
class Base {
public:
    static void fun(int x) {
        while (x-- > 0) {
            cout << x << endl;
        }
    }
};
// 调用
thread t(&Base::fun, 10);
t.join();

3. join and detach

3.1 join

  • Once a thread starts, if we want to wait for the thread to finish, we need to call join() on that object
  • A double join will cause the program to terminate
  • Before joining, we should explicitly check whether it can be joined, using joinable()
void run(int count) {
    while (count-- > 0) {
        cout << count << endl;
    }
    std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
    thread t1(run, 10);
    cout << "main()" << endl;
    t1.join();
    if (t1.joinable()) {
        t1.join();
    }
    cout << "main() after" << endl;
    return 0;
}

3.2 detach

  • This is used to detach a newly created thread from its parent thread
  • Before detaching a thread, always check whether it is joinable; otherwise it may result in two detaches, and a double detach() will cause the program to terminate
  • If we have a detached thread and the main function is returning, the detached thread's execution will be suspended
void run(int count) {
    while (count-- > 0) {
        cout << count << endl;
    }
    std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
    thread t1(run, 10);
    cout << "main()" << endl;
    t1.detach();
    if(t1.joinable())
        t1.detach();
    cout << "main() after" << endl;
    return 0;

4. Critical sections and mutexes

4.1 What is a critical section?

A critical section is a piece of code that, for the program to behave correctly, can only be executed by one thread at a time. If two threads (or processes) execute the code inside the critical section simultaneously, the program may no longer behave correctly.

4.2 Is just incrementing a variable a critical section?

Maybe.

The process of incrementing a variable (i++) takes three steps:

  • Copy the memory content into a CPU register. load
  • Increment that value in the CPU. increment
  • Store the new value back into memory. store

If that memory location (for example the variable i below) can only be accessed by one thread, there is no race condition and no critical section associated with i. But the sum variable is a global variable that can be accessed by two threads. The two threads may try to increment the variable at the same time.

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

using namespace std;

int sum = 0; //shared

mutex m;

void *countgold() {
    int i; //local to each thread
    for (i = 0; i < 10000000; i++) {
        sum += 1;
    }
    return NULL;
}

int main() {
    thread t1(countgold);
    thread t2(countgold);

    //Wait for both threads to finish
    t1.join();
    t2.join();

    cout << "sum = " << sum << endl;
    return 0;
}

The typical output of the code above is a total sum of 20000000. Because a race condition exists, every run of the program prints a different total. The code does not prevent both threads from reading and writing the sum at the same time. For example, both threads copy the current value of sum into the CPU running each thread (let us pick 123). Both threads increment their own copy by one. Both threads write that value back (124). If the threads had accessed the sum at different times, the count would be 125.

4.3 How do you ensure that only one thread can access a global variable at a time?

If one thread is currently in the critical section, we want the other thread to wait until the first thread is done. For this we can use a mutex (short for mutual exclusion).

A vivid analogy for a mutex:

bg2013042408

A simple way to keep others out is to put a lock on the door. The first person to arrive locks the door; whoever arrives later sees the lock and queues up at the door, waiting for the lock to be opened before going in. This is called a "mutex" (Mutual exclusion, abbreviated Mutex); it prevents multiple threads from reading and writing the same memory area at the same time.

m.lock();
sum += 1;
m.unlock();

With the code above, it correctly outputs: sum = 20000000.

References

http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html?utm_source=com.ideashower.readitlater.pro&utm_medium=social&utm_oi=35626384621568

https://www.youtube.com/watch?v=eZ8yKZo-PGw&list=PLk6CEY9XxSIAeK-EAh3hB4fgNvYkYmghp&index=4

Comments