【问题标题】:std::lock_guard and std::adopt_lock behaviour without locking the mutexstd::lock_guard 和 std::adopt_lock 行为而不锁定互斥锁
【发布时间】:2021-10-26 13:55:01
【问题描述】:

我一直在学习std::lockstd::lock_guard 的用法,大多数示例都遵循以下模式:

std::lock(m1, m2);
std::lock_guard<std::mutex> guard1(m1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(m2, std::adopt_lock);
//Do something here

然后我遇到了一个示例,该示例使用了与使用 std::unique_lock 时相同的模式,但使用了 lock_guard

std::lock_guard<std::mutex> guard1(m1, std::adopt_lock);
std::lock_guard<std::mutex> guard2(m2, std::adopt_lock);
std::lock(m1, m2);
//Do something here

我的问题是,如果您使用第二种模式并且在到达std::lock 之前发生异常,这会导致未定义的行为吗?

附:我知道 C++17 引入了 std::scoped_lock 并且 std::lock_guard 仍然存在,主要是为了与旧代码兼容。

【问题讨论】:

  • 我完全不明白第二种模式是如何正确的,不管有什么例外。如果m1m2 在进入此代码时没有保留,那么adopt_lock 会导致UB。如果它们被持有,那么std::lock(m1, m2) 就是UB,因为你不能锁定你已经持有的互斥锁。
  • 是的,很明显。 Lockguard 将在未锁定的互斥体上触发析构函数中的解锁。如果是 UB 或只是实现定义的错误,则不是 100%。 MSVC 将触发异常并随后终止,因为这就是异常在析构函数中所做的。
  • @NateEldredge adapt_lock 是一种用于锁卫的 NOOP。问题在于析构函数触发解锁。
  • @ALX23z: C++20 thread.lock.guard p4: lock_guard(mutex_type&amp; m, adopt_lock_t); "前提条件:调用线程拥有互斥锁m。"
  • "...如果当前线程没有持有非共享锁,行为未定义..." en.cppreference.com/w/cpp/thread/lock_guard/lock_guard Pattern (2) 不分例外都是UB。

标签: c++ c++11


【解决方案1】:

你的第二个例子是未定义的行为; adopt_lock 构造函数假定已持有互斥锁。此 UB 在构造时触发,而不是在销毁时或引发异常时触发。


如果您使用unique_lock 而不是scoped_lock,它有一个:

unique_lock( mutex_type& m, std::defer_lock_t t ) noexcept;

构造函数,它允许您使用 std::lock 稍作改动:

std::unique_lock<std::mutex> guard1(m1, std::defer_lock);
std::unique_lock<std::mutex> guard2(m2, std::defer_lock);
std::lock(guard1, guard2);

现在,unique_lock 会跟踪它是否有锁,因此可能存在内存和性能开销;如果unique_locks 在作用域中是本地的,编译器可以并且会优化它,如果它可以证明它是安全的。

如果编译器不能证明它是安全的,通常它就是不安全的。

【讨论】:

    猜你喜欢
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-04
    • 1970-01-01
    • 2018-02-15
    • 2018-05-23
    相关资源
    最近更新 更多