【问题标题】:Does a locked mutex protect the condition_variable as well as the data?锁定的互斥锁是否保护 condition_variable 以及数据?
【发布时间】:2018-05-12 16:22:56
【问题描述】:

考虑以下this_thread::sleep_for() 函数的实现。它来自 Stroustrup 的书 "CPL", 4, pg 1232。我通过 (i) 重命名和 (ii) 将函数与客户端代码分开来对其进行了修改:

#include <chrono>       /// milliseconds
#include <mutex>        /// mutex, unique_lock
#include <condition_variable>   /// condition_variable

/// declarations ...

namespace ext
{
   void sleep_for(int ms);
}


/// implementation ...

void ext::sleep_for(int ms)
{
   std::mutex mtx;
   std::condition_variable timercv;

   /// acquire mtx
   std::unique_lock<std::mutex> lck {mtx};

   /// release and reacquire mutex
   timercv.wait_for(lck, std::chrono::milliseconds {ms});
}  // implicitly release mtx


Stroustrup 表示:

互斥锁保护 wait_for() 免受数据竞争。 wait_for() 在进入睡眠状态时释放其互斥体,并在其线程未阻塞时重新获取它。


我想问一下:
1) 通过说 mutex 保护 wait_for() 免受数据竞争,我假设 Stroustrup 指的是 condition_variable 本身,对吧?

2) 为什么condition_variable 需要防止并发访问?它是一个局部变量,而不是一个全局变量。线程对ext::sleep_for() 函数的每次调用都将拥有condition_variable 的单独副本。

3)在这个程序中,没有要保护的全局数据(例如就绪标志等)。mutex 是否保护condition_variable?如果有,从何而来?正如我所说,每个调用此函数的线程都将访问一个单独的副本。

-- 编辑--
这是我对为什么在此代码中使用 mutexcondition_variable 的解释:
1) 需要mutex 才能获得对它的锁定。
2) condition_variable 释放互斥锁上的锁,进入休眠指定时间,然后重新获取锁。

【问题讨论】:

  • 您的代码出错,我不知道是不是因为源代码中的转录器(有时 Stroustrup 使用的是准标准 C++)。 wait_for 应该传递第三个参数 []{return false;} 来修复它。
  • @Yakk-AdamNevraumont 不,谓词是可选的,有一个重载,只需要一个锁和一个时间。
  • 你是对的。这是一个不好的例子。您对本地 cv 和互斥锁是正确的。没有全局数据意味着没有什么可以用锁来保护的。此外,正如@Yakk-AdamNevraumont 所说,它缺少一个谓词来保护它免受虚假唤醒。 (时间为MAX时间,但允许更小)。也许有一些奇怪的用例,但我不知道。条件变量的原始用例是允许持有互斥锁的线程休眠(也称为块)并以原子方式释放互斥锁。然后稍后以原子方式唤醒并重新获取互斥体。
  • @Yakk,我已经编辑了帖子以添加一个可能的解释,说明为什么在此代码中使用 mutexcondition_variable
  • @ssteven 我理解代码;问题是代码有错误。

标签: c++ multithreading


【解决方案1】:

您的代码中有错误。允许条件变量虚假唤醒;您有责任处理该案件。上面的代码中没有任何东西可以处理它。这里有一个简单的解决方法:

timercv.wait_for(lck, std::chrono::milliseconds {ms}, []{return false;});

现在,任何提前唤醒都将被拒绝。

这仍然是条件变量的一种极其奇怪的用法。通常它们用于在一个或多个线程之间进行通信。在这种情况下,互斥体和条件变量以及条件变量检查的东西形成了一个三元组。

Stroustrup 似乎在谈论一个更典型的案例。

在这种更典型的情况下,互斥锁保护对条件变量检查的事物的访问,并且必须锁定才能对条件变量执行某些操作。由于这些是低级线程原语,您应该遵循有效的标准模式,或者您必须准确了解每个操作的作用以及它提供的保证并证明您的代码是正确的。任何简化都会以“对孩子说谎”的形式出现,根据谎言设计新的使用模式不会有任何可靠性。

标准中描述了确切的规格,这些是您应该参考的规格。请注意,标准版本之间的规格有所不同。

简而言之,你应该找到经过验证的使用模式并使用它们,然后将它们封装起来,永远不要碰它们。

写睡眠的更简单的方法是:

void ext::sleep_for(int ms) {
  std::mutex mtx;
  std::unique_lock<std::mutex> lock1(mtx);
  std::unique_lock<std::mutex> lock2(mtx, std::defer_lock_t{});
  lock2.try_lock_for(std::chrono::milliseconds {ms});
}  // implicitly release mtx

它没有几乎那么复杂的一组条件,并且使用更简单的原语。

【讨论】:

  • Adam,我很欣赏你对 sleep_for() 的实现。为什么需要2把锁?一个还不够吗?
  • @ssteven 一个使互斥锁锁定,另一个在锁定的互斥锁上休眠。我确实忘记推迟锁定它。固定。
猜你喜欢
  • 1970-01-01
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多