繁体   English   中英

在条件变量上发出信号之前是否必须锁定互斥锁?

[英]Is it mandatory to lock mutex before signaling on condition variable?

我们已经实现了 TaskRunner,其功能将被不同的线程调用以启动、停止和发布任务。 TaskRunner 会在内部创建一个线程,如果队列不为空,它会从队列中弹出任务并执行它。 Start() 将检查线程是否正在运行。 如果没有创建一个新线程。 Stop() 将加入线程。 代码如下。

bool TaskRunnerImpl::PostTask(Task* task) {
  tasks_queue_.push_back(task);
  return true;
}

void TaskRunnerImpl::Start() {
  std::lock_guard<std::mutex> lock(is_running_mutex_);
  if(is_running_) {
    return;
  }
  is_running_ = true;

  runner_thread_ = std::thread(&TaskRunnerImpl::Run, this);
}

void TaskRunnerImpl::Run() {
  while(is_running_) {
    if(tasks_queue_.empty()) {
      continue;
    }
    Task* task_to_run = tasks_queue_.front();
    task_to_run->Run();
    tasks_queue_.pop_front();

    delete task_to_run;
  }
}

void TaskRunnerImpl::Stop() {
  std::lock_guard<std::mutex> lock(is_running_mutex_);
  is_running_ = false;
  if(runner_thread_.joinable()) {
    runner_thread_.join();
  }
}

我们现在要使用条件变量,否则线程将不断检查任务队列是否为空。 我们实现如下。

  • 线程 function (Run()) 将等待条件变量。
  • PostTask() 将在有人发布任务时发出信号。
  • 如果有人呼叫停止,Stop() 将发出信号。

代码如下。

bool TaskRunnerImpl::PostTask(Task* task) {
  std::lock_guard<std::mutex> taskGuard(m_task_mutex);
  tasks_queue_.push_back(task);
  m_task_cond_var.notify_one();
  return true;
}

void TaskRunnerImpl::Start() {
  std::lock_guard<std::mutex> lock(is_running_mutex_);
  if(is_running_) {
    return;
  }
  is_running_ = true;

  runner_thread_ = std::thread(&TaskRunnerImpl::Run, this);
}

void TaskRunnerImpl::Run() {
    while(is_running_) {
        Task* task_to_run = nullptr;

        {
            std::unique_lock<std::mutex> mlock(m_task_mutex);
            m_task_cond_var.wait(mlock, [this]() {
                return !(is_running_ && tasks_queue_.empty());
            });

            if(!is_running_) {
                return;
            }

            if(!tasks_queue_.empty()) {
                task_to_run = tasks_queue_.front();
                task_to_run->Run();
                tasks_queue_.pop_front();
            }
        }

        if(task_to_run)
            delete task_to_run;
    }
}

void TaskRunnerImpl::Stop() {
    std::lock_guard<std::mutex> lock(is_running_mutex_);
    is_running_ = false;
    m_task_cond_var.notify_one();
    if(runner_thread_.joinable()) {
        runner_thread_.join();
    }
}

我有几个问题如下。 有人可以帮助我理解这些吗?

  1. 条件变量 m_task_cond_var 与互斥量 m_task_mutex 链接。 但是 Stop() 已经将互斥量 is_running_mutex 锁定到 gaurd 'is_running_'。 我需要在发出信号之前锁定 m_task_mutex 吗? 在这里我不相信为什么要锁定 m_task_mutex 因为我们没有保护与任务队列相关的任何东西。

  2. 在 Thread 函数(Run())中,我们正在读取 is_running_ 而没有锁定 is_running_mutex。 它是否正确?

我需要在发出 [In Stop ] 信号之前锁定m_task_mutex吗?

当在condition_variable::wait方法中测试的谓词取决于信号线程中发生的事情(这几乎总是如此),那么您应该在发出信号之前获取互斥量。 如果您没有持有m_task_mutex ,请考虑以下可能性:

  1. 观察线程 ( TaskRunnerImpl::Run ) 唤醒(通过虚假唤醒或从其他地方收到通知)并获得互斥量。
  2. 观察者线程检查它的谓词并发现它是false的。
  3. 信号器线程 ( TaskRunnerImpl::Stop ) 将谓词更改为返回true (通过设置is_running_ = false; )。
  4. 信号器线程向条件变量发出信号。
  5. 观察者线程等待信号(坏)
    • 信号已经来了又走了
    • 谓词为false ,因此观察者开始等待,可能会无限期地等待。

如果您在发出信号时持有互斥锁,可能发生的最坏情况是,阻塞的线程 ( TaskRunnerImpl::Run ) 醒来并在尝试获取互斥锁时立即被阻塞。 这可能会对性能产生一些影响。


在 [ TaskRunnerImpl::Run ] 中,我们正在读取is_running_而不锁定is_running_mutex 它是否正确?

一般来说没有。 即使它是bool类型。 因为 boolean 通常作为单个字节实现,所以一个线程可能在您读取时写入该字节,从而导致部分读取。 然而,在实践中,它是安全的。 也就是说,您应该在阅读之前获得互斥量(然后立即释放)。

事实上,最好使用std::atomic<bool>而不是bool + mutex组合(或者std::atomic_flag如果你想花哨的话),这将具有相同的效果,但更容易使用。

我需要在发出 [In Stop ] 信号之前锁定m_task_mutex吗?

是的你是。 您必须在同一互斥量下更改条件,并在互斥量锁定或更改后解锁后发送信号。 如果您不使用相同的互斥锁,或者在该互斥锁被锁定之前发送信号,则会创建竞争条件,创建std::condition_variable来解决。

逻辑是这样的:

监视线程锁定互斥锁并检查监视条件。 如果没有发生,它会进入休眠状态并以原子方式解锁互斥量。 所以信号线程锁定互斥量,改变条件和信号。 如果信号线程在 watchone 锁定互斥量之前这样做,那么 watchiong 就会看到条件发生并且不会 go 进入睡眠状态。 如果它之前锁定,它将 go 睡眠并在信号线程发出信号时唤醒。

注意:您可以在互斥量解锁之前或之后向条件变量发送信号,这两种情况都是正确的,但可能会影响性能。 但是在锁定互斥量之前发出信号是不正确的。

条件变量 m_task_cond_var 与互斥量 m_task_mutex 链接。 但是 Stop() 已经将互斥量 is_running_mutex 锁定到 gaurd 'is_running_'。 我需要在发出信号之前锁定 m_task_mutex 吗? 在这里我不相信为什么要锁定 m_task_mutex 因为我们没有保护与任务队列相关的任何东西。

您使代码过于复杂并使事情变得更糟。 在这种情况下,您应该只使用一个互斥锁,它会按预期工作。

在 Thread 函数(Run())中,我们正在读取 is_running_ 而没有锁定 is_running_mutex。 它是否正确?

在 x86 硬件上它可能“工作”,但从语言的角度来看这是 UB。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM