C++ Mutex and Lock Guard
A mutex is a lock that only one thread can hold at a time. A lock guard holds a mutex for a scope and releases it when the scope ends.
Local counts avoid the shared counter
A shared counter updated without a lock can lose updates, so its total cannot be printed as a fact. This program keeps one local count per thread and stores each count in its own slot.
#include <array>
#include <iostream>
#include <thread>
void countLocal(int index, std::array<int, 3>* slots) {
int local = 0;
for (int i = 0; i < 100; i++) {
local++;
}
(*slots)[index] = local;
}
int main() {
std::array<int, 3> slots = {0, 0, 0};
std::thread first(countLocal, 0, &slots);
std::thread second(countLocal, 1, &slots);
std::thread third(countLocal, 2, &slots);
first.join();
second.join();
third.join();
int total = slots[0] + slots[1] + slots[2];
std::cout << "Total: " << total << std::endl;
return 0;
}A mutex protects a shared counter
Four threads each add one to the same counter 250 times. The lock guard holds the mutex during every single update.
#include <iostream>
#include <mutex>
#include <thread>
void addMany(int* counter, std::mutex* guard) {
for (int i = 0; i < 250; i++) {
std::lock_guard<std::mutex> lock(*guard);
++(*counter);
}
}
int main() {
int counter = 0;
std::mutex guard;
std::thread first(addMany, &counter, &guard);
std::thread second(addMany, &counter, &guard);
std::thread third(addMany, &counter, &guard);
std::thread fourth(addMany, &counter, &guard);
first.join();
second.join();
third.join();
fourth.join();
std::cout << "Total: " << counter << std::endl;
return 0;
}A mutex protects a shared vector
Three threads each push two values into one vector under the lock. Main sorts the vector after the joins because the order of arrival is not a fact.
#include <algorithm>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
void addPair(std::vector<int>* values, std::mutex* guard, int first, int second) {
std::lock_guard<std::mutex> lock(*guard);
values->push_back(first);
values->push_back(second);
}
int main() {
std::vector<int> values;
std::mutex guard;
std::thread first(addPair, &values, &guard, 40, 10);
std::thread second(addPair, &values, &guard, 30, 60);
std::thread third(addPair, &values, &guard, 20, 50);
first.join();
second.join();
third.join();
std::sort(values.begin(), values.end());
std::cout << "Values:";
for (int value : values) {
std::cout << " " << value;
}
std::cout << std::endl;
std::cout << "Size: " << values.size() << std::endl;
return 0;
}Example
#include <iostream>
#include <mutex>
#include <thread>
void addScore(int* total, std::mutex* guard, int score) {
std::lock_guard<std::mutex> lock(*guard);
*total += score;
}
int main() {
int total = 0;
std::mutex guard;
std::thread first(addScore, &total, &guard, 12);
std::thread second(addScore, &total, &guard, 18);
std::thread third(addScore, &total, &guard, 24);
std::thread fourth(addScore, &total, &guard, 46);
first.join();
second.join();
third.join();
fourth.join();
std::cout << "Board total: " << total << std::endl;
return 0;
}Key points
- A shared value updated by many threads at the same moment can lose updates.
- The lost updates happen because two threads can read the same old value before either one writes.
- std::mutex gives one lock that threads take turns holding.
- std::lock_guard takes the lock when it is made and releases it when its scope ends.
- A shared total is printed only after the lock has protected every update and every thread has joined.
In this example, four threads each add one fixed score to a shared total. The lock guard holds the mutex during each addition, so no update is lost. Main joins all four threads and then prints the board total of 100.
Note
The order values arrive in shared storage is not fixed, so shared values are sorted before they are printed.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does std::lock_guard do when its scope ends?