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!