Daily bit(e) C++. std::scoped_lock
Добавлено 29 мая 2026 в 08:54
Daily bit(e) C++ 31, блокировка с областью видимости для нескольких мьютексов без взаимоблокировок из C++17: std::scoped_lock.

Блокировка std::scoped_lock из C++17 – это блокировка RAII, которая предоставляет решение на уровне STL для получения блокировок на нескольких мьютексах без риска взаимоблокировки.
Когда блокировки не всегда получаются в одном и том же порядке, мы можем легко создать ситуацию, когда поток T1 удерживает блокировку A и пытается получить блокировку B, в то время как поток T2 удерживает блокировку B и пытается получить блокировку A, что приводит к взаимоблокировке.
Хотя та же логика может быть достигнута с помощью std::lock, этот подход требует, чтобы разблокировка обрабатывалась вызывающим.
#include <iostream>
#include <thread>
#include <mutex>
#include <random>
#include <algorithm>
#include <utility>
struct Player
{
Player(std::string name, uint64_t seed) : name_(std::move(name)),
re_(seed), dist_(1,6), mux_{}, score_{0} {}
void play_with(Player& other)
{
if (&other == this)
return;
// чтобы сыграть в игру, нам необходимо захватить и нашу блокировку,
// и блокировку оппонента, что создает потенциальную возможность
// взаимной блокировки
std::scoped_lock lock(mux_, other.mux_);
// бросать кубики до тех пор, пока один из игроков не выиграет,
// затем увеличить счет
int our = 0, them = 0;
do
{
our = roll();
them = other.roll();
}
while (our == them);
if (our > them)
score_++;
else
other.score_++;
} // блокировка освобождена
const std::string& name() const { return name_; }
int score() const { return score_; }
private:
// бросание кубика
int roll() { return dist_(re_); }
std::string name_;
std::default_random_engine re_;
std::uniform_int_distribution<int> dist_;
std::mutex mux_;
int score_;
};
int main()
{
std::random_device r;
std::vector<std::unique_ptr<Player>> players;
auto names = {"Player1", "Player2", "Player3", "Player4",
"Player5", "Player6", "Player7", "Player8",
"Player9"};
// сгенерировать игроков из names
std::ranges::transform(names, std::back_inserter(players),
[&](const char* name) {
return std::make_unique<Player>(name, r());
});
// Запуск турнира:
// каждый игрок играет со всеми остальными игроками параллельно
std::vector<std::jthread> rounds;
for (auto &v : players)
{
rounds.push_back(std::jthread([&players,&v]{
for (auto &oponent : players) {
v->play_with(*oponent);
}
}));
}
rounds.clear(); // т.е. присоединить все потоки
// сортировка и печать
std::ranges::sort(players, std::greater<>{},
[](const std::unique_ptr<Player>& p) {
return p->score();
});
for (const auto &v : players)
{
std::cout << v->name() << " " << v->score() << "\n";
}
}
