代码之家  ›  专栏  ›  技术社区  ›  sam

std::lock如何与std::unique_lock对象一起工作,而不是直接与std::mutex一起工作?

  •  2
  • sam  · 技术社区  · 2 年前

    我正在使用一段涉及银行账户转账的多线程代码。目标是安全地在账户之间转账,而不会遇到比赛条件。我正在使用 std::mutex 在转账过程中保护银行账户余额:

    我的问题集中在 std::unique_lock 具有 std::lock 。而不是通过 std::互斥 对象直接到 std::锁定 ,我把它们包起来 std::unique_lock 并将其传递给 std::锁定

    如何 std::锁定 一起工作 std::unique_lock 对象?

    std::锁定 负责实际锁定 from to 互斥,而 std::unique_lock 对象仅仅管理锁(即,当它们超出范围时释放它们)?

    std::锁定 呼叫 lock() 方法 std::unique_lock ?

    使用的优点是什么 std::unique_lock 具有 std::锁定 过直通过 std::互斥 对象 std::锁定 ?

    struct bank_account
    {
        bank_account(int balance) :
            mtx(), balance{ balance }
    
        {}
        std::mutex mtx;
        int balance;
    };
    
    void transfer(bank_account& from, bank_account& to, int amount)
    {
        std::unique_lock<std::mutex> from_Lock(from.mtx, std::defer_lock);
        std::unique_lock<std::mutex> to_Lock(to.mtx, std::defer_lock);
        std::lock(from_Lock, to_Lock);
        
        if (amount <= from.balance)
        {
            std::cout << "Before:    " << amount << " from: " << from.balance << " to: " << to.balance << '\n';
            from.balance -= amount;
            to.balance += amount;
            std::cout << "After:     " << amount << " from: " << from.balance << " to: " << to.balance << '\n';
        }
        else
        {
            std::cout << amount << " is greater than " << from.balance << '\n';
        }
    }
    
    int main()
    {
        bank_account A(200);
        bank_account B(100);
        std::vector<std::jthread> workers;
        workers.reserve(20);
        for (int i = 0; i < 10; ++i)
        {
            workers.emplace_back(transfer, std::ref(A), std::ref(B), 20);
            workers.emplace_back(transfer, std::ref(B), std::ref(A), 10);
        }
    }
    
    1 回复  |  直到 2 年前
        1
  •  5
  •   Jan Schultke    2 年前

    目的 std::lock 是提供无死锁锁定(请参阅 libc++ implementation )的 可锁定 对象。 经典的问题是,如果你有两把锁 L1 L2

    • 单线程锁 L1 然后 L2
    • 另一个线程锁定 L2 然后 L1 ,

    那么可能会出现死锁,因为每个线程可能持有一个锁,而需要另一个线程提供另一个锁。此问题适用于锁定 from.mtx to.mtx 在:

    std::unique_lock<std::mutex> from_Lock(from.mtx, std::defer_lock);
    std::unique_lock<std::mutex> to_Lock(to.mtx, std::defer_lock);
    std::lock(from_Lock, to_Lock);
    

    std::锁定 的死锁解除锁定吗 from_Lock to_Lock std::unique_lock 做其余的(即RAII的东西)。

    Q&A.

    如何 std::锁定 一起工作 std::unique_lock 对象?
    std::锁定 呼叫 lock() 方法 std::unique_lock ?

    std::unique_lock 可锁定 std::锁定 将呼叫 锁定() 然后 锁定() s互斥。

    std::锁定 负责实际锁定 from to 互斥,而 std::unique_lock 对象仅仅管理锁(即,当它们超出范围时释放它们)?

    std::unique_lock 完全能够自己锁定和解锁互斥对象。它唯一不能做的就是在涉及多个锁时实现无死锁锁定。

    使用的优点是什么 std::unique_lock 具有 std::锁定 过直通过 std::mutex 对象 std::锁定 ?

    之后,您必须手动解锁两个互斥对象,这很容易出错。这是一个类似于 std::unique_ptr 与。 new / delete 。如果您立即将两个互斥对象都封装在 std::lock_guard 虽然

    进一步改进

    用于 std::锁定 ,你可以用一把比 std::unique_lock :

    std::lock(from.mtx, to.mtx);
    std::lock_guard<std::mutex> from_lock(from.mtx, std::adopt_lock);
    std::lock_guard<std::mutex> to_lock(to.mtx, std::adopt_lock);
    

    你只需要 std::unique_lock 如果你想转让所有权;否则您可以使用 std::lock_guard (这是一种稍微简单的类型)。

    如果您使用的是C++17,那么使用 std::scoped_lock :

    // CTAD, equivalent to std::scoped_lock<std::mutex, std::mutex> lock(...)
    std::scoped_lock lock(from.mtx, to.mtx);
    

    std::scoped_lock 是的替代品 std::lock_guard 并且在构造函数中内置了无死锁锁定,类似于使用 std::锁定