C++ Basics: Lock Two Related Counters with std::scoped_lock
Update two mutex-protected counters as one operation and verify a deterministic final total.
Prepare a scratch directory
Tested on Linux x86_64 with GCC 14.2.0. Install a C++20-capable GCC compiler and its standard-library development files, with g++ on PATH. You also need Bash, a text editor, and mktemp, rm, and rmdir. No third-party library is required.
Keep the same terminal open. Stop if setup fails before saving the source:
start_directory="$PWD"
work_directory="$(mktemp -d)" || exit 1
cd -- "$work_directory" || exit 1Run the example
Save this as scoped-lock-two-mutexes-demo.cpp:
#include <iostream>
#include <mutex>
#include <thread>
int main()
{
std::mutex first_mutex;
std::mutex second_mutex;
int first_count = 0;
int second_count = 0;
auto increment_both = [&] {
for (int iteration = 0; iteration < 1000; ++iteration) {
std::scoped_lock guard(first_mutex, second_mutex);
++first_count;
++second_count;
}
};
std::jthread first_worker(increment_both);
std::jthread second_worker(increment_both);
first_worker.join();
second_worker.join();
std::cout << "First: " << first_count << '\n';
std::cout << "Second: " << second_count << '\n';
std::cout << "Total: " << first_count + second_count << '\n';
}Compile and run it. The second command runs only after compilation succeeds:
g++ -std=c++20 -Wall -Wextra -Werror -pedantic -pthread scoped-lock-two-mutexes-demo.cpp -o scoped-lock-two-mutexes-demo &&
./scoped-lock-two-mutexes-demoObserved output:
First: 2000
Second: 2000
Total: 4000Read the result
Each named guard acquires both distinct mutexes and releases them at the end of the loop body. Both increments are protected. Joining both workers before printing ensures the final reads happen after their updates, giving two thousand increments per counter.
Apply it carefully
Every concurrent access to these counters must follow the same locking discipline. scoped_lock uses a deadlock-avoidance algorithm when acquiring multiple mutexes, but it cannot fix deadlocks caused by unrelated locks held elsewhere. Do not pass the same non-recursive mutex twice. For counters that always move together, one mutex would usually be simpler; this example isolates the two-lock technique.
The C++ working draft documents the relevant behavior. This example uses C++20 features, not newer additions shown elsewhere in the current draft.
Clean up
Remove only the source and executable created here, then remove the empty practice directory:
rm -- scoped-lock-two-mutexes-demo.cpp scoped-lock-two-mutexes-demo
cd -- "$start_directory" || exit 1
rmdir -- "$work_directory"
unset start_directory work_directoryIf the directory is not empty, inspect its remaining contents before removing anything else.
Acknowledgements
This post was created with assistance from AI.