C++: Multithreading Basics

Last updated: 2026-08-26

In lesson 42, we learned about regular expressions.

Now, we'll enter the advanced territory of C++ — multithreaded programming.

Modern computers are all multi-core, but single-threaded programs can only use one core — what a waste.

Multithreading lets you do multiple things at once, dramatically improving performance.


1. Multithreading Overview

(1) 1.1 What Is a Thread?

A thread is the smallest unit of program execution.

Process vs Thread:

Real-life analogy:


(2) 1.2 Why Use Multithreading?

Advantage Description
Improved performance Multi-core parallel computing
Better responsiveness UI thread doesn't block
Simplified design Assign different tasks to different threads


2. Creating Threads

(1) 2.1 Basic Usage

C++11 provides the std::thread class in the thread header file.

Example: Creating a thread (Difficulty ⭐)

▶ Example 1: Multithreading programming demo (Difficulty ⭐)

TEXT 📖 Display only
#include <iostream>
#include <thread>

void hello() {
 std::cout << "Hello from thread!" << std::endl;
}

int main() {
 std::thread t(hello); // Create thread
 t.join(); // Wait for thread to finish
 
 std::cout << "Main thread ends" << std::endl;
 return 0;
}

Output:

TEXT 📖 Display only
Main thread: starting
Main thread: waiting for child thread
Child thread: Hello from thread
Main thread: ended

Run result:

TEXT 📖 Display only
Hello from thread!
Main thread ends

(2) 2.2 join vs detach

Function Purpose Description
join() Wait for thread to finish Blocks the current thread
detach() Detach thread Thread runs independently, can no longer be joined

Example: Waiting with join (Difficulty ⭐)

CPP
#include <iostream>
### ▶ Example 2: Multithreading programming demo (Difficulty ⭐)

#include <thread>
#include <chrono>

void worker(int id) {
 for (int i = 0; i < 3; i++) {
 std::cout << "Worker " << id << " working..." << std::endl;
 std::this_thread::sleep_for(std::chrono::milliseconds(500));
 }
}

int main() {
 std::thread t1(worker, 1);
 std::thread t2(worker, 2);
 
 t1.join(); // Wait for t1 to finish
 t2.join(); // Wait for t2 to finish
 
 std::cout << "All workers done" << std::endl;
 return 0;
}


3. Passing Arguments to Threads

(1) 3.1 Passing Parameters

The std::thread constructor can accept any callable object and arguments.

Example: Passing parameters (Difficulty ⭐⭐)

TEXT 📖 Display only
#include <iostream>
#include <thread>
#include <string>

void printMessage(std::string msg, int count) {
 for (int i = 0; i < count; i++) {
 std::cout << msg << std::endl;
 }
}

int main() {
 std::thread t(printMessage, "Hello", 3);
 t.join();
 
 return 0;
}

(2) 3.2 Passing by Reference

By default, arguments are passed by value. To pass by reference, you must use std::ref.

Example: Passing by reference (Difficulty ⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <functional>

void increment(int& x) {
 x++;
}

int main() {
 int counter = 0;
 std::thread t(increment, std::ref(counter));
 t.join();
 
 std::cout << "Counter: " << counter << std::endl; // Output: 1
 return 0;
}


4. Mutexes

(1) 4.1 Why Do We Need Mutexes?

Problem: Multiple threads accessing shared data simultaneously causes data races.

Example: Data race (Difficulty ⭐⭐)

TEXT 📖 Display only
#include <iostream>
#include <thread>
#include <vector>

int counter = 0;

void increment() {
 for (int i = 0; i < 1000; i++) {
 counter++; // Multiple threads modifying simultaneously, result is uncertain
 }
}

int main() {
 std::vectorstd::thread threads;
 for (int i = 0; i < 10; i++) {
 threads.emplace_back(increment);
 }
 
 for (auto& t : threads) {
 t.join();
 }
 
 std::cout << "Counter: " << counter << std::endl; // Expected 10000, actual may be less
 return 0;
}

(2) 4.2 Protecting Shared Data with a Mutex

A mutex (Mutex) ensures that only one thread accesses shared data at a time.

Example: Protecting with mutex (Difficulty ⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment() {
 for (int i = 0; i < 1000; i++) {
 mtx.lock(); // Lock
 counter++;
 mtx.unlock(); // Unlock
 }
}

int main() {
 std::vectorstd::thread threads;
 for (int i = 0; i < 10; i++) {
 threads.emplace_back(increment);
 }
 
 for (auto& t : threads) {
 t.join();
 }
 
 std::cout << "Counter: " << counter << std::endl; // Always 10000
 return 0;
}

(3) 4.3 lock_guard — RAII Style

Recommended: Use std::lock_guard for automatic locking/unlocking.

TEXT 📖 Display only
void increment() {
 for (int i = 0; i < 1000; i++) {
 std::lock_guardstd::mutex lock(mtx); // Locks on construction, unlocks on destruction
 counter++;
 } // Automatically unlocks
}


5. Condition Variables

(1) 5.1 Why Do We Need Condition Variables?

Problem: A thread needs to wait for a condition to become true (e.g., queue is not empty).

Solution: std::condition_variable


(2) 5.2 Example: Producer-Consumer (Difficulty ⭐⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queueint q;
std::mutex mtx;
std::condition_variable cv;

void producer() {
 for (int i = 0; i < 10; i++) {
 std::lock_guardstd::mutex lock(mtx);
 q.push(i);
 std::cout << "Produced: " << i << std::endl;
 cv.notify_one(); // Notify consumer
 }
}

void consumer() {
 for (int i = 0; i < 10; i++) {
 std::unique_lockstd::mutex lock(mtx);
 cv.wait(lock, { return !q.empty(); }); // Wait until queue is not empty
 
 int value = q.front();
 q.pop();
 std::cout << "Consumed: " << value << std::endl;
 }
}

int main() {
 std::thread t1(producer);
 std::thread t2(consumer);
 
 t1.join();
 t2.join();
 
 return 0;
}

Output:

TEXT 📖 Display only
Produced: 
Consumed: 


6. Asynchronous Tasks

(1) 6.1 std::async

std::async is used to launch an asynchronous task and returns a std::future.

Example: Asynchronous computation (Difficulty ⭐⭐)

TEXT 📖 Display only
#include <iostream>
#include <future>

int calculate(int x) {
 return x * x;
}

int main() {
 std::futureint result = std::async(calculate, 10);
 std::cout << "Result: " << result.get() << std::endl; // Output: 100
 
 return 0;
}

❓ FAQ

Q: How many threads should I use? A: Typically equal to the number of CPU cores. Too many threads cause context switching overhead.


Q: What is a deadlock? A: Two threads waiting for each other to release locks, so neither can continue.

How to avoid:


Q std::thread or OpenMP — which is better?
A - std::thread: Flexible, cross-platform - OpenMP: Simple, good for scientific computing

▶ Example 3: Creating threads (Difficulty ⭐)

CPP
#include <iostream>
#include <thread>

void printNumbers(int start, int end) {
    for (int i = start; i <= end; i++) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::thread t1(printNumbers, 1, 5);
    std::thread t2(printNumbers, 10, 15);

    t1.join();
    t2.join();

    return 0;
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
 

Example output:

TEXT 📖 Display only
1 10 2 11 3 12 4 13 5 14 15
💡 Tip: std::thread creates threads, join() waits for them to finish. Output order may vary due to thread scheduling.


Key Point Summary
std::thread Create threads
join/detach Wait/detach threads
std::mutex Mutex, protects shared data
std::lock_guard RAII-style locking
std::condition_variable Condition variable, inter-thread communication
std::async Asynchronous tasks

📖 Summary

📝 Exercises

  1. Basic (Difficulty ⭐): Create two threads that output "Thread A" and "Thread B" respectively, and observe the randomness of the output order.

  2. Intermediate (Difficulty ⭐⭐): Create 4 threads, each computing a cumulative sum over a range of numbers (e.g., 1-2500, 2501-5000...), then combine the results at the end.

  3. Challenge (Difficulty ⭐⭐⭐): Use std::async and std::future to implement a concurrent download simulator: create 3 asynchronous tasks, each simulating downloading a different-sized file, and wait for all to complete before summarizing.



Next lesson: Multithreading Synchronization (#44)

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏