Programming lesson
Mastering C++ Multithreading: A Hands-On Guide to Spawning Threads with Random Number Generation
Learn how to create and manage multiple threads in C++ with this step-by-step tutorial. We'll build a program that spawns 10 threads to search for a target random number, exploring join(), race conditions, and the nice utility.
Why Multithreading Matters in 2026
In today's world of AI apps, real-time gaming, and high-frequency trading, programs must do multiple things at once. Think about a battle royale game like Fortnite: while you're running, the game is also rendering graphics, processing network packets, and updating AI enemies. That's multithreading in action. In C++, threads let your program execute multiple paths concurrently, squeezing maximum performance from multi-core CPUs.
Understanding the Threading Exercise
This tutorial is based on a classic C++ assignment: spawn 10 threads, each searching for a random number that matches a target. The catch? Threads finish in random order due to race conditions. We'll walk through the solution step by step, explaining each concept.
What You'll Learn
- How to create threads with
std::thread - Using
join()to wait for threads - Passing arguments to thread functions
- Random number generation with
rand()andsrand() - Race conditions and the
niceutility
Setting Up Your C++ Environment
You'll need a C++ compiler with pthread support. Compile with: g++ threads.cpp -o ex5.out -pthread -std=c++11. Ensure your virtual machine has at least 512 MB RAM and multiple cores to observe thread interleaving.
Step 1: Accept a Command-Line Argument
Your program must take one integer argument. Use argc and argv:
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: ./ex5.out <target>\n";
return 1;
}
int target = std::atoi(argv[1]);
// ...
}Step 2: Write the Thread Function
The function will generate random numbers between 0 and 9999 until it finds the target. It needs an ID (0-9) and the target number.
void searchNumber(int id, int target) {
std::srand(std::time(nullptr) + id); // seed per thread
int random;
do {
random = std::rand() % 10000;
} while (random != target);
std::cout << "Thread " << id << " completed." << std::endl;
}Note: Seeding with time(nullptr) + id ensures each thread gets a different sequence, making them less likely to finish together.
Step 3: Spawn 10 Threads in a Loop
Use a loop to create threads, storing them in an array or vector. Do not write 10 separate std::thread calls.
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(searchNumber, i, target);
}Step 4: Join All Threads
Call join() on each thread to ensure main waits for all to finish before printing the final message.
for (int i = 0; i < 10; ++i) {
threads[i].join();
}
std::cout << "All threads have finished finding numbers." << std::endl;Understanding Race Conditions and the nice Utility
By default, threads may finish in order if the CPU is fast. To force random order, lower your process priority using nice. Run: nice -n 19 ./ex5.out 1414. This gives your process the lowest priority, making the OS interrupt it more often, causing threads to complete at different times. This simulates real-world race conditions where thread scheduling is unpredictable.
Why This Matters
In AI training pipelines, data processing threads must be synchronized. In gaming, thread timing affects frame rates. Understanding race conditions helps you write robust concurrent code.
Full Example Code
#include <iostream>
#include <thread>
#include <cstdlib>
#include <ctime>
void searchNumber(int id, int target) {
std::srand(std::time(nullptr) + id);
int random;
do {
random = std::rand() % 10000;
} while (random != target);
std::cout << "Thread " << id << " completed." << std::endl;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: ./ex5.out <target>\n";
return 1;
}
int target = std::atoi(argv[1]);
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(searchNumber, i, target);
}
for (int i = 0; i < 10; ++i) {
threads[i].join();
}
std::cout << "All threads have finished finding numbers." << std::endl;
return 0;
}Testing Your Program
Run twice with nice -n 19 ./ex5.out 1414. Observe that thread completion order changes each run. Screenshot both outputs for submission.
Common Pitfalls
- Forgetting to join threads can cause program to exit before threads finish.
- Using same seed for all threads may cause them to finish together.
- Not compiling with
-pthreadwill cause linking errors.
Beyond the Assignment: Real-World Threading
Modern C++ offers std::async, std::future, and thread pools. In 2026, with the rise of AI assistants like ChatGPT, threading is crucial for handling multiple user requests concurrently. Even in mobile apps, background threads handle network calls without freezing the UI.
Trend Connection: AI Chatbots
When you ask an AI chatbot a question, your request is handled by a thread in a server pool. Each thread processes your query independently, similar to our threads searching for a random number. The server must manage hundreds of threads concurrently, ensuring fair scheduling and avoiding deadlocks.
Conclusion
You've built a multithreaded C++ program that spawns 10 threads, each searching for a target random number. You've learned about thread creation, joining, race conditions, and the nice utility. This foundation prepares you for more advanced topics like mutexes, condition variables, and thread pools. Keep experimenting with different thread counts and priorities to see how concurrency affects performance.
"Threads allow your program to do more than one thing at a time, but with great power comes great responsibility—always join your threads!"