C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
105: Threads with std::thread
I've been thinking about how to handle heavy lifting in our applications. Imagine we're building a simple file indexer—something that scans a bunch of "files" (simulated strings) for a specific keyword. If we do this on the main thread, our whole program just freezes until the scan is done. That's a terrible user experience.
The Freeze
I'll start by writing a basic search function that takes a while to run. I'm using std::this_thread::sleep_for here just to simulate a heavy I/O operation or a complex regex search.
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <chrono>
void scan_files(std::string keyword) {
std::cout < "[Worker] Scanning files for: " < keyword < "...\n";
std::this_thread::sleep_for(std::chrono::seconds(3)); // Simulate work
std::cout < "[Worker] Found 42 matches for " < keyword < "!\n";
}
int main() {
std::cout < "Starting indexer...\n";
scan_files("cpp_tutorial");
std::cout < "Done!\n";
return 0;
}
When I run this, it's boring. The program prints "Starting indexer...", hangs for three seconds, and then prints "Done!". If this were a GUI app, the window would be "Not Responding". I want that scan to happen in the background so I can do other things in main().
The Crash and the "Join"
Right, let's throw std::thread at this. I'll wrap that scan_files call in a thread object to kick it off into the background.
int main() {
std::cout < "Starting indexer...\n";
std::thread worker(scan_files, "cpp_tutorial");
std::cout < "I can do other things while the worker scans!\n";
return 0;
}
I ran this, and it crashed. Immediately. The console screamed something about std::terminate being called. I forgot a crucial rule: a std::thread object must be either joined or detached before it is destroyed.
Essentially, the worker object in main went out of scope (because the program ended) while the actual OS thread was still running. C++ doesn't know if you want to wait for that thread to finish or let it run wild in the background, so it just kills the whole process to be safe. I'll use .join(), which tells the main thread: "Stop here and wait until the worker is completely finished."
int main() {
std::cout < "Starting indexer...\n";
std::thread worker(scan_files, "cpp_tutorial");
std::cout < "I can do other things while the worker scans!\n";
worker.join(); // Wait for the worker to finish
std::cout < "Worker finished. Now we can exit.\n";
return 0;
}
Now it works. The "I can do other things" message prints immediately, and then the program pauses at worker.join() until the three-second sleep is over.
Passing Data Safely
Now, what if I want to pass a large object to the thread? Let's say a vector of filenames. If I just pass it as a reference, I might run into a disaster where the main thread modifies or deletes the vector while the worker is still reading it.
I noticed that std::thread copies the arguments by value by default. That's actually a safety feature. But if I really want to pass by reference (maybe for performance with a massive dataset), I can't just put &my_vector in the constructor. std::thread handles arguments in a way that requires std::ref to explicitly signal a reference.
Let's look at how that works in practice:
void scan_list(const std::vector<std::string>& files) {
std::cout < "[Worker] Scanning " < files.size() < " files...\n";
}
int main() {
std::vector<std::string> my_files = {"doc1.txt", "doc2.txt", "image.png"};
// This would fail to compile or behave unexpectedly:
// std::thread worker(scan_list, my_files);
// Use std::ref to pass by reference
std::thread worker(scan_list, std::ref(my_files));
worker.join();
return 0;
}
Just a warning: when you use std::ref, you are now responsible for the lifetime of that object. If main returned before the worker finished, the worker would be reading a deleted vector. That's why join() is your best friend here.
📋 Practical Task
Build a Multi-Threaded Log Processor
Create a program that simulates processing three different log files simultaneously.
- Create a function
void process_log(int log_id)that prints "Processing log [id]...", sleeps for a random duration between 1 and 3 seconds (usingstd::this_thread::sleep_for), and then prints "Log [id] complete!". - In your
mainfunction, spawn three separate threads, each processing a different log ID (1, 2, and 3). - Ensure the main thread prints "All logs are being processed in parallel..." before it begins waiting for the threads.
- Properly
join()all three threads before the program exits to prevent a crash.
There are no comments for now.