C++ Multithreading Basics: std::thread, mutex, and Concurrency
이 글의 핵심
Practical introduction to C++ multithreading—threads, mutexes, common bugs, and patterns you can use in real code.
Create default thread
#include <iostream>
#include <thread>
using namespace std;
void printHello() {
cout << "Hello from thread!" << endl;
}
int main() {
thread t(printHello);// create thread
t.join();// Wait for thread to terminate
cout << "Main thread" << endl;
return 0;
}
Lambda and parameters
#include <iostream>
#include <thread>
using namespace std;
int main() {
// use lambda
thread t1( {
cout << "Lambda thread" << endl;
});
// Passing parameters
thread t2( {
cout << x << ": " << s << endl;
}, 10, "Hello");
t1.join();
t2.join();
return 0;
}
Synchronize with mutex
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 1000; i++) {
mtx.lock();
counter++;
mtx.unlock();
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Counter: " << counter << endl; // 2000
return 0;
}
lock_guard (RAII)
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
void safeIncrement(int& counter) {
for (int i = 0; i < 1000; i++) {
lock_guard<mutex> lock(mtx); // unlocks automatically
counter++;
} // automatically unlocked
}
int main() {
int counter = 0;
thread t1(safeIncrement, ref(counter));
thread t2(safeIncrement, ref(counter));
t1.join();
t2.join();
cout << "Counter: " << counter << endl; // 2000
return 0;
}
Practical example
Example 1: Parallel computation
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void sumRange(int start, int end, long long& result) {
long long sum = 0;
for (int i = start; i < end; i++) {
sum += i;
}
result = sum;
}
int main() {
const int N = 100000000;
const int NUM_THREADS = 4;
vector<thread> threads;
vector<long long> results(NUM_THREADS);
int range = N / NUM_THREADS;
// create thread
for (int i = 0; i < NUM_THREADS; i++) {
int start = i * range;
int end = (i == NUM_THREADS - 1) ? N : (i + 1) * range;
threads.emplace_back(sumRange, start, end, ref(results[i]));
}
// wait for all threads
for (auto& t : threads) {
t.join();
}
// sum up results
long long total = 0;
for (long long r : results) {
total += r;
}
cout << "sum: " << total << endl;
return 0;
}
Description: Split large calculations into multiple threads for parallel processing.
Example 2: Producer-Consumer Pattern
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
using namespace std;
queue<int> dataQueue;
mutex mtx;
condition_variable cv;
bool done = false;
void producer() {
for (int i = 1; i <= 10; i++) {
this_thread::sleep_for(chrono::milliseconds(100));
{
lock_guard<mutex> lock(mtx);
dataQueue.push(i);
cout << "produce: " << i << endl;
}
cv.notify_one();// Notify consumer
}
{
lock_guard<mutex> lock(mtx);
done = true;
}
cv.notify_all();
}
void consumer() {
while (true) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, {
return !dataQueue.empty() || done;
});
while (!dataQueue.empty()) {
int value = dataQueue.front();
dataQueue.pop();
lock.unlock();
cout << "consume: " << value << endl;
this_thread::sleep_for(chrono::milliseconds(150));
lock.lock();
}
if (done && dataQueue.empty()) {
break;
}
}
}
int main() {
thread prod(producer);
thread cons(consumer);
prod.join();
cons.join();
return 0;
}
Description: This is a pattern where a producer and consumer exchange data through a queue.
Example 3: Thread Pool
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std;
class ThreadPool {
private:
vector<thread> workers;
queue<function<void()>> tasks;
mutex mtx;
condition_variable cv;
bool stop;
public:
ThreadPool(size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; i++) {
workers.emplace_back([this]() {
while (true) {
function<void()> task;
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this]() {
return stop || !tasks.empty();
});
if (stop && tasks.empty()) {
return;
}
task = move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
unique_lock<mutex> lock(mtx);
stop = true;
}
cv.notify_all();
for (auto& worker : workers) {
worker.join();
}
}
void enqueue(function<void()> task) {
{
unique_lock<mutex> lock(mtx);
tasks.push(task);
}
cv.notify_one();
}
};
int main() {
ThreadPool pool(4);
for (int i = 1; i <= 10; i++) {
pool.enqueue([i]() {
cout << "task " << i << " start (thread "
<< this_thread::get_id() << ")" << endl;
this_thread::sleep_for(chrono::seconds(1));
cout << "task " << i << " done" << endl;
});
}
this_thread::sleep_for(chrono::seconds(5));
return 0;
}
Description: Efficiently distributes work to thread pools.
Frequently occurring problems
Problem 1: Race Condition
Symptoms: Results vary each time
Cause: Accessing shared resources without synchronization
dissolvent:
// ❌ Race conditions
int counter = 0;
void increment() {
for (int i = 0; i < 1000; i++) {
counter++;// danger!
}
}
// ✅ Protected by mutex
mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 1000; i++) {
lock_guard<mutex> lock(mtx);
counter++;
}
}
Problem 2: Deadlock
Symptom: Program freezes
Cause: Waiting on different mutexes
dissolvent:
// ❌ Deadlock possible
mutex mtx1, mtx2;
void func1() {
lock_guard<mutex> lock1(mtx1);
lock_guard<mutex> lock2(mtx2);
}
void func2() {
lock_guard<mutex> lock2(mtx2);// Different order!
lock_guard<mutex> lock1(mtx1);
}
// ✅ Always lock in the same order
void func1() {
lock_guard<mutex> lock1(mtx1);
lock_guard<mutex> lock2(mtx2);
}
void func2() {
lock_guard<mutex> lock1(mtx1);// same order
lock_guard<mutex> lock2(mtx2);
}
// ✅ Use scoped_lock (C++17)
void func() {
scoped_lock lock(mtx1, mtx2);// Automatically prevent deadlock
}
Problem 3: See dangling after detach
Symptom: Crashes or strange values
Cause: The variable referenced by the thread is destroyed.
dissolvent:
// ❌ Dangerous code
void badExample() {
int data = 10;
thread t([&data]() {
this_thread::sleep_for(chrono::seconds(1));
cout << data << endl;// Already destroyed!
});
t.detach();
} // data destruction
// ✅ Capture by value
void goodExample() {
int data = 10;
thread t([data]() {
this_thread::sleep_for(chrono::seconds(1));
cout << data << endl;// safety
});
t.detach();
}
// ✅ Wait with join
void betterExample() {
int data = 10;
thread t([&data]() {
cout << data << endl;
});
t.join();// atmosphere
}
FAQ
Q1: join vs detach?
A:
- join: Wait for thread to terminate (recommended)
- detach: Runs in background (needs caution)
Q2: How many threads should I create?
A: Typically, the number of CPU cores is sufficient.
unsigned int numThreads = thread::hardware_concurrency();
Q3: Are mutexes slow?
A: There is some overhead, but you should definitely use it if necessary.You might also consider atomic.
Q4: Which containers are thread-safe?
A: C++ standard containers are not thread-safe by default.Protect it with a mutex or use a concurrent library.
Q5: async vs thread?
A:
- thread: low-level control
- async: high level, simple (returns future)
auto future = async(launch::async, {
return 42;
});
cout << future.get() << endl;
Q6: When to use multithreading?
A:
- Parallelize CPU-intensive tasks
- Utilize I/O latency
- Improved responsiveness (UI)
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ jthread |“Auto-Join Threads” Guide
- C++ scoped_lock |“Scope Lock” Guide
- C++ thread pool |“Thread Pool” implementation guide
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intention of the code clear?
- Are there enough test cases?
- Is it documented?
Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, multithreading, thread, mutex, concurrency, etc.
Related articles
- C++ Atomic |
- C++ multithreaded crash |
- 2-week Go language (Golang) master curriculum for C++ developers
- 30 C++ technical interview questions |
- C++ jthread |