【问题标题】:How to properly solve the producer consumer in C++11如何在 C++11 中正确解决生产者消费者
【发布时间】:2017-06-15 22:15:28
【问题描述】:

我正在尝试解决 C++11 中的生产者消费者问题。 我有一个保存资源的对象,多个线程可以 添加或消耗这些资源。我的问题是当我尝试实施 该对象上的“可用时使用”方法。 请假设插入/删除操作非常复杂。

代码中逻辑的一点解释。

struct ResourceManager{
  std::mutex mux;
  std::unique_lock lock{mux};
  std::condition_variable bell;

  void addResource(/*some Resource*/){
    lock.lock();
    //add resource
    lock.unlock();
    bell.notify_one(); //notifies waiting consumer threads to consume
  }

  T getResource(){
    while(true){
      lock.lock();
      if(/*resource is available*/){
        //remove resource from the object
        lock.unlock();
        return resource;
      }else{
        //new unique lock mutex object wmux creation
        lock.unlock(); //problem line
        bell.wait(wmux); //waits until addResource rings the bell
        continue;
      }
    }
  }

};

假设以下场景:
- 两个线程,T1,T2,几乎同时调用addResource,getResource。

-T2 锁定互斥体,发现没有更多可用资源,
所以它必须阻塞,直到有新资源可用。
所以它解锁互斥锁并设置铃声等待。

-T1 运行匹配更快。当互斥锁解锁时,
它立即添加资源,并在 T2 设置等待铃之前,
T1已经按门铃了,没有人通知。

-T2 无限期等待铃声响起,但不会添加更多资源。

我假设锁定互斥锁的线程可能是唯一的 解锁它。因此,如果我在解锁互斥锁之前尝试调用 bell.wait, 互斥锁永远无法解锁。

如果可能,我想使用不等待时间或多次检查的解决方案。
那么在 C++11 中我可以通过哪种方式解决这个问题呢?

【问题讨论】:

  • 可能与您的问题无关,但请使用 std::unique_lock 之类的锁守卫来锁定/解锁互斥锁。
  • 锁是唯一锁
  • 什么是wmux?您应该锁定lock,并执行bell.wait(lock),以避免出现竞争条件。
  • @FrustratedSoul 哦,对不起。但这不是如何使用它。通常你没有一个成员变量,而是一个局部变量,当它离开它的范围时,它只是解锁互斥锁。
  • @FrustratedSoul:我已经删除了我的解决方案,并邀请您在en.cppreference.com/w/cpp/thread/condition_variable/wait 上检查 unique_lock 和 condition_variable 的正确用法,该示例几乎正是您想要做的。

标签: c++ multithreading c++11 mutex


【解决方案1】:
    lock.unlock(); //problem line
    bell.wait(wmux); //waits until addResource rings the bell

是的,这确实是问题所在。

要按设计正确使用条件变量,您不要wait()ing 对其相关条件变量之前解锁互斥锁。条件变量上的wait()ing 在等待期间自动解锁它,并在线程被notify()-ed 后重新获取互斥锁。 unlock-and-wait 和 wake-up-after-being-notified-and-lock 都是原子操作。

Allnotify()s 应该在互斥锁被锁定时发出。所有wait() 也在互斥锁完全锁定时完成。鉴于notify(),正如我所提到的,是原子的,这导致所有与互斥锁相关的操作都是原子的和完全排序的,包括管理受互斥锁保护的资源,以及通过条件变量进行线程通知,现在受互斥锁保护也是。

有些设计模式可以在不使用互斥保护的情况下通知条件变量。但是它们更难正确实现并且仍然实现线程安全语义。除了互斥锁保护的所有其他内容之外,让所有条件变量操作也受互斥锁保护,实现起来要简单得多。

【讨论】:

  • 感谢您的回答。因此,如果我理解正确,在 addResource 中,应该在互斥锁解锁之前调用 bell.notify_one()。另外,在getResource中,我应该删除lock.unlock(),用bell.wait(lock)代替bell.wait(wmux);我做对了吗?
  • 听起来不错;当然这取决于实际的实现细节,因为你只是在你的问题中总结了你的代码。注意the requirement for wait():“锁 - std::unique_lock<:mutex> 类型的对象,必须由当前线程锁定”。
【解决方案2】:

std::condition_variable::wait 需要在您的互斥体上传递一个锁定的std::unique_lockwait 将解锁互斥锁作为其操作的一部分,并在它返回之前重新锁定它。

使用像std::lock_guardstd::unique_lock 这样的锁守卫的正常方法是在本地构造它们,并让它们的构造函数锁定您的互斥体,而它们的析构函数将其解锁。

此外,您可以通过为std::condition_variable::wait 提供谓词来避免原始代码中的外部while 循环。

struct ResourceManager {
  std::mutex mux;
  std::condition_variable bell;

  void addResource(T resource)
  {
    std::lock_guard<std::mutex> lock{mux};
    // Add the resource
    bell.notify_one();
  }

  T getResource()
  {
     std::unique_lock<std::mutex> lock{mux};
     bell.wait(lock, [this](){ return resourceIsAvailable(); });
     return // the ressource
  }
};

【讨论】:

  • 感谢您抽出宝贵时间,您的回答很好地描述了其他人之前已经在这里回答过的内容。可惜他删除了他的帖子:S
猜你喜欢
  • 2015-12-14
  • 1970-01-01
  • 2016-08-08
  • 1970-01-01
  • 2014-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多