C++ Threads
A thread is a separate line of work inside one program. Several threads can run at the same time on different processor cores, which makes long calculations faster and keeps programs responsive. In the browser, the editor reloads the page in a special threads mode the first time a program uses threads; your files are kept.
Starting and joining threads
#include <iostream>
#include <thread>
#include <vector>
void work(int id, long long& result) {
long long sum = 0;
for (int i = 1; i <= 1000; i++) sum += i * id;
result = sum;
}
int main() {
std::vector<long long> results(3);
std::vector<std::thread> threads;
for (int id = 0; id < 3; id++) {
threads.emplace_back(work, id + 1, std::ref(results[id]));
}
for (auto& t : threads) t.join(); // wait for every thread
for (long long r : results) std::cout << r << "\n";
return 0;
}500500 1001000 1501500
std::thread starts a function in a new thread; its arguments follow. std::ref passes a reference (threads copy their arguments otherwise). join waits until a thread has finished; every thread must be joined (or detached) before its std::thread object goes away, or the program stops with an error. C++20's std::jthread joins by itself.
Each thread above writes to its own element of results, so the threads never touch the same data.
Shared data and mutex
When several threads change the same variable, changes can be lost: this is a race condition, and it makes results differ from run to run. A std::mutex lets only one thread at a time into the protected code; std::lock_guard locks it and unlocks it automatically at the end of the block.
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
std::mutex m;
long long total = 0;
void addRange(int from, int to) {
long long local = 0;
for (int i = from; i <= to; i++) local += i;
std::lock_guard<std::mutex> lock(m); // one thread at a time
total += local;
}
int main() {
std::vector<std::thread> workers;
for (int t = 0; t < 4; t++) {
workers.emplace_back(addRange, t * 250000 + 1, (t + 1) * 250000);
}
for (auto& w : workers) w.join();
std::cout << "Sum of 1 to 1000000 = " << total << "\n";
return 0;
}Sum of 1 to 1000000 = 500000500000
Each thread adds its numbers in a local variable first and locks only once for the final addition; locking for every single number would be correct but slow.
Atomic counters
For a single counter, std::atomic is simpler than a mutex:
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
int main() {
std::atomic<int> count{0};
std::vector<std::thread> t;
for (int i = 0; i < 4; i++) {
t.emplace_back([&count] { for (int k = 0; k < 10000; k++) count++; });
}
for (auto& th : t) th.join();
std::cout << count << "\n";
return 0;
}40000
async and future
std::async runs a function, possibly in another thread, and returns a std::future from which get() collects the result. It is the easiest way to compute several results in parallel:
#include <future>
#include <iostream>
long long sumTo(long long n) {
long long s = 0;
for (long long i = 1; i <= n; i++) s += i;
return s;
}
int main() {
auto a = std::async(std::launch::async, sumTo, 1000);
auto b = std::async(std::launch::async, sumTo, 2000);
std::cout << a.get() + b.get() << "\n";
return 0;
}2501500
Practice exercises
- Remove the
lock_guardfrom the mutex example, run it several times and compare the results. - Use
std::asyncto find the largest element of each half of a vector in parallel. - Count the prime numbers below 100000 with four threads, each checking a quarter of the range.