Daily bit(e) C++. std::scoped_lock

Добавлено 29 мая 2026 в 08:54

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

Daily bit(e) C++

Блокировка 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";
    }
}

Пример на Compiler Explorer

Теги

C++ / CppMutex / МьютексSTL / Standard Template Library / Стандартная библиотека шаблоновМногопоточностьПрограммирование