C++多线程:使用std::packaged_task在线程之间传递任务

C++多线程:使用std::packaged_task在线程之间传递任务

贺志国

std::packaged_task其实就是std::function的多线程版本,用于在线程之间传递任务。主线程与后台线程之间往往需要传递一些任务。例如:主线程是图形化界面显示,后台线程执行计算任务。主线程收到用户的某项长周其计算任务时,可以调用后台线程来执行,等到计算结果返回后再在图形化界面中显示,C++ 11中的 std::packaged_task<...>对象适合该任务。另一项需要考虑的事情是:线程之间的相互通信通过什么来实现?需要用循环来不断检查吗?可以使用循环,更好地方式是通过触发一个事件来唤醒线程。C++11中提供了一个很不错的数据结构 std::condition_variable来处理该问题。以下是示例代码。

#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <future>
#include <iostream>
#include <limits>
#include <mutex>
#include <thread>

using namespace std::literals::chrono_literals;
using TaskFunc = int(void);

int Add(const int a, const int b) {
  // Simulate a longer calculation process
  std::this_thread::sleep_for(100ms);

  return a + b;
}

int Subtract(const int a, const int b) {
  // Simulate a longer calculation process
  std::this_thread::sleep_for(100ms);

  return a - b;
}

int Multiply(const int a, const int b) {
  // Simulate a longer calculation process
  std::this_thread::sleep_for(100ms);

  return static_cast<int>(static_cast<double>(a) * static_cast<double>(b));
}

int Divide(const int a, const int b) {
  // Simulate a longer calculation process
  std::this_thread::sleep_for(100ms);

  if (0 == b) {
    return std::numeric_limits<int>::max();
  }
  return static_cast<int>(static_cast<double>(a) / static_cast<double>(b));
}

class TaskDispatcher {
 public:
  void Shutdown(const bool stop = true) {
    is_stop_.store(stop);
    task_cond_.notify_one();
  };

  template <typename F, typename... Args>
  auto PostTaskForMainThread(F&& f, Args&&... args)
      -> std::future<typename std::result_of<F(Args...)>::type> {
    // using return_type = typename std::result_of<F(Args...)>::type;
    std::packaged_task<TaskFunc> task(
        std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    auto res = task.get_future();
    {
      std::lock_guard<std::mutex> locker(task_mutex_);
      tasks_.emplace_back(std::move(task));
    }
    task_cond_.notify_one();

    return res;
  }

  void BackgroundThreadFunc() {
    while (!is_stop_.load()) {
      std::packaged_task<TaskFunc> task;
      {
        std::unique_lock<std::mutex> locker(task_mutex_);
        // Wait for a signal to perform a task or stop.
        task_cond_.wait(
            locker, [this]() { return !tasks_.empty() || is_stop_.load(); });
        // Ensure that the task queue is not empty.
        if (!tasks_.empty()) {
          task = std::move(tasks_.front());
          tasks_.pop_front();
        }
      }

      // The current thread may be woken up by a stop signal.
      if (task.valid()) {
        // Note that the task performing time may be long, task() cannot be
        // placed in a locked code snippet.
        task();
      }
    }
  }

 private:
  std::mutex task_mutex_;
  std::condition_variable task_cond_;
  std::deque<std::packaged_task<TaskFunc>> tasks_;
  std::atomic<bool> is_stop_{false};
};

int main() {
  TaskDispatcher passing_tasks;

  // The two ways for the thread's spawning are equivalent
  // Method 1
  // std::thread background_thread(&TaskDispatcher::BackgroundThreadFunc,
  //                           &passing_tasks);

  // Method 2
  auto background_task =
      std::async(std::launch::async, &TaskDispatcher::BackgroundThreadFunc,
                 &passing_tasks);

  auto add_result = passing_tasks.PostTaskForMainThread(&Add, 5, 10);
  std::cout << "add_result = " << add_result.get() << '\n';
  auto substract_result = passing_tasks.PostTaskForMainThread(&Subtract, 23, 9);
  std::cout << "substract_result = " << substract_result.get() << '\n';
  auto multiply_result = passing_tasks.PostTaskForMainThread(&Multiply, 27, 12);
  std::cout << "multiply_result = " << multiply_result.get() << '\n';
  auto divide_result = passing_tasks.PostTaskForMainThread(&Divide, 81, 9);
  std::future_status status;
  do {
    // Add your logic here.
    status = divide_result.wait_for(20ms);
    if (status == std::future_status::deferred) {
      std::cout << "deferred\n";
    } else if (status == std::future_status::timeout) {
      std::cout << "timeout\n";
    } else if (status == std::future_status::ready) {
      std::cout << "ready!\n";
    }
  } while (status != std::future_status::ready);
  std::cout << "divide_result = " << divide_result.get() << '\n';

  passing_tasks.Shutdown();

  // Method 1
  // background_thread.join();

  // Method 2
  background_task.wait();

  std::cout << "Finished!\n";

  return 0;
}

CMake编译文件如下:

cmake_minimum_required(VERSION 3.0.0)
project(pass_tasks_between_threads VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 14)

add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp)

find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})

include(CTest)
enable_testing()
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

使用cmake的编译:

 mkdir build && cd build
 
# 生成Makefile
cmake ..

# 构建目标,也可直接使用:make
 cmake --build .

如直接使用g++编译,指令为:

g++ -g -Wall -std=c++17 -pthread pass_tasks_between_threads.cpp -o pass_tasks_between_threads

要求GCC编译器版本必须为9.1以上版本(Ubuntu 20.04 2021年以后的版本默认就是GCC 9.3)。

运行结果如下:

./pass_tasks_between_threads 
add_result = 15
substract_result = 14
multiply_result = 324
timeout
timeout
timeout
timeout
ready!
divide_result = 9
Finished!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值