簡體   English   中英

std :: condition_variable - 通知一次但等待線程喚醒兩次

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

這是一個簡單的C ++線程池實現。 它是一個修改過的版本,來自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__ */

和測試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;
}

我在這個測試程序中有一個錯誤。 只有一個任務提交給線程池,但我得到了兩次喚醒的工作線程:

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

我認為問題出在FixedThreadPool :: ThreadWorker()本身。 工作線程持續等待條件變量來獲取新任務。 函數FixedThreadPool :: Submit()將一個新任務添加到隊列並調用condition.nofity_one()來喚醒一個工作線程。

但我無法弄清楚工作線程如何被喚醒兩次。 我只提交了一個任務,並且在此測試中只有一個工作線程。

將評論轉換為答案:

condition_variable::wait(lock, pred)等同於while(!pred()) wait(lock); 如果pred()返回true則實際上不會發生等待,並且調用立即返回。

您的第一個喚醒來自notify_one()調用; 第二個“喚醒”是因為第二個wait()調用恰好在Stop()調用之后執行,所以你的謂詞返回true並且wait()立即返回而不等待。

很明顯你在這里得到了(不)幸運:如果第二次wait()調用發生在Stop()調用之前,那么你的工作線程將永遠等待(沒有虛假的喚醒),所以你的主線程。

另外,擺脫__THREAD_POOL_H__ 將那些雙下划線燒到地上。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM