简体   繁体   English

std :: condition_variable - 通知一次但等待线程唤醒两次

[英]std::condition_variable – notify once but wait thread wakened twice

Here's a simple C++ thread pool implementation. 这是一个简单的C ++线程池实现。 It's an altered version orginated from https://github.com/progschj/ThreadPool . 它是一个修改过的版本,来自https://github.com/progschj/ThreadPool

#ifndef __THREAD_POOL_H__
#define __THREAD_POOL_H__

#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>

namespace ThreadPool {

class FixedThreadPool {
 public:
  FixedThreadPool(size_t);

  template<class F, class... Args>
  auto Submit(F&& f, Args&&... args)
      -> std::future<typename std::result_of<F(Args...)>::type>;

  template<class F, class... Args>
  void Execute(F&& f, Args&&... args);

  ~FixedThreadPool();

  void AwaitTermination();

  void Stop();

 private:
  void ThreadWorker();

  // need to keep track of threads so we can join them
  std::vector<std::thread> workers;

  // the task queue
  std::queue< std::function<void()> > tasks;

  // synchronization
  std::mutex worker_mutex;
  std::mutex queue_mutex;
  std::condition_variable condition;

  // stop flag
  bool stop_;

  // thread size
  int thread_size_;
};

// Constructor does nothing. Threads are created when new task submitted.
FixedThreadPool::FixedThreadPool(size_t num_threads): 
    stop_(false),
    thread_size_(num_threads) {}

// Destructor joins all threads
FixedThreadPool::~FixedThreadPool() {
  //std::this_thread::sleep_for(std::chrono::seconds(5));
  for(std::thread &worker: workers) {
    if (worker.joinable()) {
      worker.join();
    }
  }
}

// Thread worker
void FixedThreadPool::ThreadWorker() {
  std::function<void()> task;
  while (1) {
    {
      std::unique_lock<std::mutex> lock(this->queue_mutex);
      this->condition.wait(lock,
                     [this]() { return this->stop_ || !this->tasks.empty(); });
      printf("wakeeeeeened\n");
      if (this->stop_ && this->tasks.empty()) {
        printf("returning ...\n");
        return;
      }
      task = std::move(this->tasks.front());
      this->tasks.pop();
    }
    task();
  }
}

// Add new work item to the pool
template<class F, class... Args>
auto FixedThreadPool::Submit(F&& f, Args&&... args)
    -> std::future<typename std::result_of<F(Args...)>::type >
{
  {
    std::unique_lock<std::mutex> lock(this->worker_mutex);
    if (workers.size() < thread_size_) {
      workers.emplace_back(std::thread(&FixedThreadPool::ThreadWorker, this));
    }
  }

  using return_type = typename std::result_of<F(Args...)>::type;

  auto task = std::make_shared< std::packaged_task<return_type()> >(
      std::bind(std::forward<F>(f), std::forward<Args>(args)...)
  );

  std::future<return_type> res = task->get_future();
  {
    std::unique_lock<std::mutex> lock(queue_mutex);
    if(stop_) {
      throw std::runtime_error("ThreadPool has been shutdown.");
    }
    tasks.emplace([task]() { (*task)(); });
  }
  condition.notify_one();
  return res;
}

// Execute new task without returning std::future object.
template<class F, class... Args>
void FixedThreadPool::Execute(F&& f, Args&&... args) {
  Submit(std::forward<F>(f), std::forward<Args>(args)...);
}

// Blocks and wait for all previously submitted tasks to be completed.
void FixedThreadPool::AwaitTermination() {
  for(std::thread &worker: workers) {
    if (worker.joinable()) {
      worker.join();
    }
  }
}

// Shut down the threadpool. This method does not wait for previously submitted
// tasks to be completed.
void FixedThreadPool::Stop() {
  printf("Stopping ...\n");
  {
    std::unique_lock<std::mutex> lock(queue_mutex);
    stop_ = true;
  }
}


} // namespace ThreadPool

#endif /* __THREAD_POOL_H__ */

and the test main.cpp: 和测试main.cpp:

#include <iostream>
#include <vector>
#include <chrono>
#include <exception>

#include "ThreadPool.h"

int main(int argc, char** argv) {
  ThreadPool::FixedThreadPool pool(3);

  pool.Execute([]() {
      std::cout << "hello world" << std::endl;
    }
  );
  pool.Stop();
  pool.AwaitTermination();
  std::cout << "All tasks complted." << std::endl;

  return 0;
}

I have a bug in this test program. 我在这个测试程序中有一个错误。 Only one task is submitted to threadpool, but I got working thread being wakened up twice: 只有一个任务提交给线程池,但我得到了两次唤醒的工作线程:

>>./test 
Stopping ...
wakeeeeeened
hello world
wakeeeeeened
returning ...
All tasks complted.

I think the problem is in FixedThreadPool::ThreadWorker() itself. 我认为问题出在FixedThreadPool :: ThreadWorker()本身。 The working thread continuously wait on a conditional variable to get new tasks. 工作线程持续等待条件变量来获取新任务。 The function FixedThreadPool::Submit() adds a new task to queue and call condition.nofity_one() to waken a working thread. 函数FixedThreadPool :: Submit()将一个新任务添加到队列并调用condition.nofity_one()来唤醒一个工作线程。

But I can't figure out how the working thread can be wakened twice. 但我无法弄清楚工作线程如何被唤醒两次。 I have only one task submitted and exactly one working thread in this test. 我只提交了一个任务,并且在此测试中只有一个工作线程。

Converting comments into answer: 将评论转换为答案:

condition_variable::wait(lock, pred) is equivalent to while(!pred()) wait(lock); condition_variable::wait(lock, pred)等同于while(!pred()) wait(lock); . If pred() returns true then no wait actually takes place and the call returns immediately. 如果pred()返回true则实际上不会发生等待,并且调用立即返回。

Your first wake is from the notify_one() call; 您的第一个唤醒来自notify_one()调用; the second "wake" is because the second wait() call happens to execute after the Stop() call, so your predicate returns true and wait() returns immediately without waiting. 第二个“唤醒”是因为第二个wait()调用恰好在Stop()调用之后执行,所以你的谓词返回true并且wait()立即返回而不等待。

It should be obvious that you got (un)lucky here: if the second wait() call took place before the Stop() call, then your worker thread will be stuck waiting forever (in the absence of spurious wakeups), and so will your main thread. 很明显你在这里得到了(不)幸运:如果第二次wait()调用发生在Stop()调用之前,那么你的工作线程将永远等待(没有虚假的唤醒),所以你的主线程。

Also, get rid of __THREAD_POOL_H__ . 另外,摆脱__THREAD_POOL_H__ Burn those double underscores to the ground. 将那些双下划线烧到地上。

暂无
暂无

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

相关问题 std::condition_variable wait() 和 notify_one() 同步 - std::condition_variable wait() and notify_one() synchronization std :: condition_variable-等待多个线程通知观察者 - std::condition_variable - Wait for several threads to notify observer 在调用 std::condition_variable::wait() 之前多次调用 std::condition_variable::notify_one() - Calling std::condition_variable::notify_one() multiple times before std::condition_variable::wait() is called std :: condition_variable在其他线程的std :: condition_variable :: notify_all()之后无法正确唤醒 - std::condition_variable not properly wakes up after std::condition_variable::notify_all() from other thread std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别 - Difference between std::atomic and std::condition_variable wait, notify_* methods std::condition_variable 是第一次检查条件,还是必须等待某人发出通知? - std::condition_variable checks the condition for the first time, or do you have to wait for someone to make a notify? 为什么std :: condition_variable的notify和wait函数都需要一个锁定的互斥锁 - Why do both the notify and wait function of a std::condition_variable need a locked mutex 为什么std :: condition_variable无法正常工作? 总是在等待之前通知 - Why std::condition_variable does not work properly? Notify always before wait std :: condition_variable :: with谓词等待 - std::condition_variable::wait with predicate std :: condition_variable wait_for无限 - std::condition_variable wait_for infinite
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM