Very simple threads synchronisation (🟡 Intermediate)
Simplify synchronization with lock_ in xtd.
Modern C++ code​
#include <array>
#include <mutex>
#include <print>
#include <thread>
auto main() ->int {
auto unsafe_value = 0;
auto safe_value = 0;
auto threads = std::array<std::thread, 10> {};
for (auto& t : threads)
t = std::thread([&]() {
static auto inc_mutex = std::mutex {};
for (auto index = 0; index < 1000; ++index) {
++unsafe_value;
{
auto lock = std::lock_guard<std::mutex>(inc_mutex);
++safe_value;
}
std::this_thread::yield();
}
});
for (auto& t : threads)
t.join();
std::println("unsafe_value = {}", unsafe_value);
std::println("safe_value = {}", safe_value);
}
- Requires an external std::mutex
- Verbose and syntactically heavy with lock_guard
- Limited flexibility (can only lock on std::mutex)
xtd code​
The following code lock on safe_value value reference :​
#include <xtd/xtd>
auto main() -> int {
auto unsafe_value = 0;
auto safe_value = 0;
auto threads = fixed_array<thread, 10> {};
for (auto& t : threads)
t = thread::start_new([&]() {
for (auto index = 0; index < 1000; ++index) {
++unsafe_value;
lock_(safe_value) {
++safe_value;
}
thread::yield();
}
});
for (auto& t : threads)
t.join();
println("unsafe_value = {}", unsafe_value);
println("safe_value = {}", safe_value);
}
- No need for a std::mutex
- We focus on what we want to protect, not how to do it
- The code is closer to the developer's real intention
The following code lock on "increment lock" value reference :​
#include <xtd/xtd>
auto main() -> int {
auto unsafe_value = 0;
auto safe_value = 0;
auto threads = fixed_array<thread, 10> {};
for (auto& t : threads)
t = thread::start_new([&]() {
for (auto index = 0; index < 1000; ++index) {
++unsafe_value;
lock_("increment lock") {
++safe_value;
}
thread::yield();
}
});
for (auto& t : threads)
t.join();
println("unsafe_value = {}", unsafe_value);
println("safe_value = {}", safe_value);
}
- Useful when you want a named critical area (for example shared between modules)
- A very difficult case to reproduce properly with std::mutex, or even impossible without a complex global map
- Immediately readable, which helps maintain and understand multi-threaded code
- In xtd, xtd::fixed_array replaces std::array to stay consistent with the framework style.
With lock_, you write what you want to do, not how the system should manage it. The lock becomes a hidden implementation detail.
Conclusion​
In a single line, lock_ encapsulates all the synchronization logic. No need to manage mutex, no need to clutter your code. And since it is based on xtd::threading::monitor, it offers you a re-entrant, elegant, and easy-to-use synchronization.
lock_("mylock") {
// first level
lock_("mylock") {
// second level (re-entrant)
}
}
Multi-threading has never been so readable in C++.
Pro tip lock_ works with any referenceable object or string. You can synchronize on this, global variables, or even temporary objects — just be sure they live long enough to avoid undefined behavior.
See xtd::threading for more information on the different thread synchronization objects in xtd.