S9.0 What This Teaches
- Creating threads with
std::thread join()anddetach()- Passing arguments to threads
std::mutexandstd::lock_guard- Data races and how to prevent them
S9.1 Creating a Thread
#include <thread>
#include <iostream>
void say_hello(int id) {
std::cout << "hello from thread " << id << "\n";
}
int main() {
std::thread t(say_hello, 1);
t.join(); // wait for t to finish before continuing
std::cout << "done\n";
}
std::thread starts immediately on construction. Call
join() before the thread object is destroyed, or the
program will terminate with an error.
S9.2 join and detach
std::thread t(say_hello, 2);
// Option 1: join - caller blocks until t finishes
t.join();
// Option 2: detach - t runs independently; caller does not wait
// std::thread t2(say_hello, 3);
// t2.detach(); // t2 is now a daemon thread; do NOT access t2 after this
join() for threads that need to finish before
the program continues. Use detach() only for fire-and-forget
background tasks, and ensure the thread does not reference local variables
that will go out of scope.
S9.3 Passing Arguments
void add(int a, int b, int& result) {
result = a + b;
}
int main() {
int r = 0;
std::thread t(add, 3, 4, std::ref(r)); // std::ref wraps reference args
t.join();
std::cout << r << "\n"; // 7
}
std::ref(). Use lambdas for cleaner capture of local state.
S9.4 mutex and lock_guard
#include <mutex>
std::mutex mtx;
int counter = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx); // locked on construction
++counter;
} // lock released here (RAII)
std::mutex protects shared data. std::lock_guard
acquires the mutex on construction and releases it on destruction - no
manual unlock needed. Always use RAII wrappers rather than calling
lock()/unlock() directly.
S9.5 Vector of Threads
std::mutex mtx;
int shared = 0;
void worker(int id) {
std::lock_guard<std::mutex> lock(mtx);
++shared;
std::cout << "thread " << id << " -> " << shared << "\n";
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
threads.emplace_back(worker, i);
for (auto& t : threads)
t.join();
std::cout << "final: " << shared << "\n";
}
S9.6 Example - All Together
// Threads - std::thread, join, mutex, lock_guard, shared data.
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <string>
std::mutex g_mutex;
void worker(int id, int& shared_count) {
std::lock_guard<std::mutex> lock(g_mutex);
++shared_count;
std::cout << "thread " << id << " incremented count to " << shared_count << "\n";
}
int main() {
int count = 0;
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i)
threads.emplace_back(worker, i, std::ref(count));
for (auto& t : threads)
t.join();
std::cout << "final count: " << count << "\n";
return 0;
}
thread 0 incremented count to 1
thread 1 incremented count to 2
thread 2 incremented count to 3
thread 3 incremented count to 4
final count: 4
S9.7 Exercise
Exercise
- Launch 5 threads, each of which appends its id to a
std::vector<int>(protected by a mutex). After joining all threads, print the vector. - Remove the mutex from the example above and run it many times. Observe the data race. Re-add the mutex to fix it.
- Write a function
parallel_sumthat splits avector<int>in half, sums each half in a separate thread, and returns the total.
S9.8 Common Mistakes
Forgetting to join (or detach)
void bad() {
std::thread t([]{ std::cout << "hi\n"; });
} // destructor of t calls std::terminate() if t is joinable
join() or detach() before the
thread object is destroyed. Use a RAII wrapper or
std::jthread (C++20) to automate this.Accessing a moved-from thread
std::thread t(worker, 0, std::ref(count));
std::thread u = std::move(t);
t.join(); // error: t no longer owns the thread after move
Detached thread accessing destroyed local variables
void bad() {
int local = 42;
std::thread t([&local]{ std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << local; }); // dangling ref
t.detach();
} // local destroyed; thread still running
S9.9 Key Terms
| Term | Meaning |
|---|---|
| std::thread | Represents a single OS thread; starts on construction |
| join() | Block the calling thread until this thread finishes |
| detach() | Allow thread to run independently; caller does not wait |
| std::ref() | Wrap a reference so it can be passed to a thread constructor |
| std::mutex | Mutual-exclusion primitive; only one thread holds it at a time |
| std::lock_guard | RAII mutex wrapper; locks on construction, unlocks on destruction |
| data race | Two threads access the same memory concurrently, at least one writing, without synchronization - undefined behavior |
| std::jthread | C++20 thread that automatically joins on destruction |