【问题标题】:std::unique_lock<std::mutex> or std::lock_guard<std::mutex>?std::unique_lock<std::mutex> 还是 std::lock_guard<std::mutex>?
【发布时间】:2013-12-29 06:52:30
【问题描述】:

我有两个用例。

A.我想同步访问两个线程的队列。

B.我想同步两个线程对队列的访问并使用条件变量,因为其中一个线程将等待另一个线程将内容存储到队列中。

对于用例 A,我看到了使用 std::lock_guard&lt;&gt; 的代码示例。对于用例 B,我看到了使用 std::unique_lock&lt;&gt; 的代码示例。

这两者有什么区别,我应该在哪个用例中使用哪一个?

【问题讨论】:

  • // 需要 "Unqiue_Lock" 而不是 "std::Lock_Guard" : (For Conditional Wait()) 为什么你需要 std::unique_lock 而不是 std::lock_guard——等待线程必须在等待时解锁互斥锁,然后再次锁定它,并且“std::lock_guard 不提供这种灵活性”。如果互斥锁在线程休眠时保持锁定,则数据准备线程将无法锁定互斥锁以将项目添加到队列中,并且等待线程将永远无法看到其条件满足

标签: c++ multithreading c++11 mutual-exclusion stdmutex


【解决方案1】:

使用lock_guard,除非您需要能够手动unlock 之间的互斥体而不破坏lock

特别是,condition_variable 在调用 wait 进入睡眠状态时会解锁其互斥锁。这就是为什么 lock_guard 在这里是不够的。

如果您已经使用 C++17 或更高版本,请考虑使用 scoped_lock 作为 lock_guard 的略微改进版本,具有相同的基本功能。

【讨论】:

  • 将 lock_guard 传递给条件变量的等待方法之一会很好,因为无论出于何种原因,等待结束时总是重新获取互斥锁。然而,该标准仅提供了 unique_lock 的接口。这可以被视为标准的缺陷。
  • @Chris 在这种情况下你仍然会破坏封装。等待方法需要能够从lock_guard 中提取互斥锁并解锁它,从而暂时破坏守卫的类不变量。尽管这对用户来说是不可见的,但我认为这是在这种情况下不允许使用 lock_guard 的正当理由。
  • 如果是这样,它将是不可见且不可检测的。 gcc-4.8 做到了。 wait(unique_lock&) 调用 __gthread_cond_wait(&_M_cond, __lock.mutex()->native_handle())(参见 libstdc++-v3/src/c++11/condition_variable.cc),它调用 pthread_cond_wait()(参见 libgcc /ghr-posix.h)。 lock_guard 也可以这样做(但不是因为它不在 condition_variable 的标准中)。
  • @Chris 重点是lock_guard 根本不允许检索底层互斥锁。这是一个故意的限制,以允许更简单地推理使用 lock_guard 的代码,而不是使用 unique_lock 的代码。实现您所要求的唯一方法是故意破坏lock_guard 类的封装并将其实现暴露给不同的类(在本例中为condition_variable)。对于条件变量的用户不必记住两种锁类型之间的区别的可疑优势,这是一个艰难的代价。
  • @Chris 您从哪里得知condition_variable_any.wait 可以与lock_guard 一起使用?该标准要求提供的锁类型满足BasicLockable 要求(§30.5.2),而lock_guard 不满足。只有它的底层互斥体可以,但由于我之前指出的原因,lock_guard 的接口不提供对互斥体的访问。
【解决方案2】:

不同之处在于您可以锁定和解锁std::unique_lockstd::lock_guard 只会在构造时锁定一次,在销毁时解锁。

因此,对于用例 B,您肯定需要 std::unique_lock 作为条件变量。情况 A 取决于您是否需要重新锁定防护装置。

std::unique_lock 具有其他功能,例如:在不立即锁定互斥锁的情况下构建,而是构建 RAII 包装器(请参阅here)。

std::lock_guard 还提供了方便的 RAII 包装器,但不能安全地锁定多个互斥锁。当您需要有限范围的包装器时可以使用它,例如:成员函数:

class MyClass{
    std::mutex my_mutex;
    void member_foo() {
        std::lock_guard<mutex_type> lock(this->my_mutex);            
        /*
         block of code which needs mutual exclusion (e.g. open the same 
         file in multiple threads).
        */

        //mutex is automatically released when lock goes out of scope
    }           
};

为了澄清 chmike 的问题,默认情况下 std::lock_guardstd::unique_lock 是相同的。 因此,在上述情况下,您可以将std::lock_guard 替换为std::unique_lock。但是,std::unique_lock 的开销可能会多一些。

请注意,现在(自 C++17 起)人们应该使用 std::scoped_lock 而不是 std::lock_guard

【讨论】:

  • 使用指令 std::unique_lock<:mutex> lock(myMutex);互斥锁会被构造函数锁定吗?
  • @chmike 是的,它会的。添加了一些说明。
  • @chmike 好吧,我认为与其说是效率问题,不如说是功能问题。如果std::lock_guard 足以满足您的情况 A,那么您应该使用它。它不仅避免了不必要的开销,而且还向读者表明了你永远不会解锁这个守卫的意图。
  • @chmike:理论上是的。然而 Mutices 并不完全是轻量级结构,因此 unique_lock 的额外开销可能与实际锁定和解锁互斥体的成本相比相形见绌(如果编译器没有优化该开销,这是可能的)。
  • So for usecase B you definitely need a std::unique_lock for the condition variable - 是的仅在cv.wait()s 的线程中,因为该方法以原子方式释放互斥锁。在您更新共享变量然后调用cv.notify_one() 的另一个线程中,一个简单的lock_guard 足以将互斥锁锁定在范围内......除非您正在做任何我无法想象的更精细的事情!例如en.cppreference.com/w/cpp/thread/condition_variable - 为我工作:)
【解决方案3】:

lock_guardunique_lock 几乎是一回事; lock_guard 是受限版本,界面受限。

lock_guard 从构造到销毁始终持有锁。 unique_lock 可以在不立即锁定的情况下创建,可以在其存在的任何时候解锁,并且可以将锁的所有权从一个实例转移到另一个实例。

所以你总是使用lock_guard,除非你需要unique_lock 的功能。 condition_variable 需要 unique_lock

【讨论】:

  • A condition_variable needs a unique_lock. - 是的仅在wait()ing方面,正如我对inf的评论中所述。
【解决方案4】:

正如其他人所提到的,std::unique_lock 跟踪互斥锁的锁定状态,因此您可以将锁定推迟到锁构建之后,并在锁销毁之前解锁。 std::lock_guard 不允许这样做。

std::condition_variable 等待函数似乎没有理由不采用 lock_guard 和 unique_lock,因为每当等待结束时(无论出于何种原因),互斥锁都会自动重新获取,这样就不会导致任何语义冲突。但是根据标准,要将 std::lock_guard 与条件变量一起使用,您必须使用 std::condition_variable_any 而不是 std::condition_variable。

编辑:删除“使用 pthreads 接口 std::condition_variable 和 std::condition_variable_any 应该相同”。在查看 gcc 的实现:

  • std::condition_variable::wait(std::unique_lock&) 只是在底层 pthread 条件变量上调用 pthread_cond_wait() 相对于 unique_lock 持有的互斥锁(因此同样可以对 lock_guard 执行相同操作,但不会因为标准没有规定)
  • std::condition_variable_any 可以与任何可锁定对象一起使用,包括根本不是互斥锁的对象(因此它甚至可以与进程间信号量一起使用)

【讨论】:

    【解决方案5】:

    lock_guardunique_lock 之间有一些共同点,也有一些区别。

    但是在所问问题的上下文中,编译器不允许将lock_guard 与条件变量结合使用,因为当线程调用等待条件变量时,互斥锁会自动解锁,而当其他线程/线程通知并调用当前线程(退出等待),重新获取锁。

    这种现象违背了lock_guard的原则。 lock_guard 只能构造一次,只能破坏一次。

    因此lock_guard 不能与条件变量结合使用,但unique_lock 可以(因为unique_lock 可以多次锁定和解锁)。

    【讨论】:

    • he compiler does not allow using a lock_guard in combination with a condition variable 这是错误的。它确实确实允许并与lock_guardnotify()ing 端完美配合。只有wait()int 端需要unique_lock,因为wait() 在检查条件时必须释放锁。
    【解决方案6】:

    它们并不是真正相同的互斥体,lock_guard&lt;muType&gt;std::mutex 几乎相同,不同之处在于它的生命周期在作用域的末尾结束(称为 D-tor),因此对这两个互斥体有一个明确的定义:

    lock_guard&lt;muType&gt; 具有在作用域块期间拥有互斥锁的机制。

    unique_lock&lt;muType&gt; 是一个包装器,允许延迟锁定、时间受限的锁定尝试、递归锁定、锁定所有权转移以及与条件变量一起使用。

    这是一个示例实现:

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    #include <functional>
    #include <chrono>
    
    using namespace std::chrono;
    
    class Product{
    
       public:
    
           Product(int data):mdata(data){
           }
    
           virtual~Product(){
           }
    
           bool isReady(){
           return flag;
           }
    
           void showData(){
    
            std::cout<<mdata<<std::endl;
           }
    
           void read(){
    
             std::this_thread::sleep_for(milliseconds(2000));
    
             std::lock_guard<std::mutex> guard(mmutex);
    
             flag = true;
    
             std::cout<<"Data is ready"<<std::endl;
    
             cvar.notify_one();
    
           }
    
           void task(){
    
           std::unique_lock<std::mutex> lock(mmutex);
    
           cvar.wait(lock, [&, this]() mutable throw() -> bool{ return this->isReady(); });
    
           mdata+=1;
    
           }
    
       protected:
    
        std::condition_variable cvar;
        std::mutex mmutex;
        int mdata;
        bool flag = false;
    
    };
    
    int main(){
    
         int a = 0;
         Product product(a);
    
         std::thread reading(product.read, &product);
         std::thread setting(product.task, &product);
    
         reading.join();
         setting.join();
    
    
         product.showData();
        return 0;
    }
    

    在这个例子中,我使用了unique_lock&lt;muType&gt;condition variable

    【讨论】:

      【解决方案7】:

      一个缺失的区别是: std::unique_lock 可以移动,但std::lock_guard 不能移动。

      注意:两者都不能复制。

      【讨论】:

      • 正在寻找这个... +1
      猜你喜欢
      • 2017-03-28
      • 2012-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-24
      相关资源
      最近更新 更多