C++异步callable包装器 std::packaged_task

std::packaged_task

std::packaged_task使得你可以对callable写一个简单的包装器,稍候再调用。(译者:类似线程池啊)
想要用好std::packaged_task,你必须处理好这四个步骤:

  • 1、包装task
  • 2、创建future
  • 3、执行计算
  • 4、获取结果

简单程序示例一下上面的步骤:

// packagedTask.cpp

#include 
#include 
#include 
#include 
#include 

class SumUp {
public:
    int operator()(int beg, int end) {
        long long int sum{ 0 };
        for (int i = beg; i < end; ++i) sum += i;
        return sum;
    }
};

int main() {

    std::cout << std::endl;

    SumUp sumUp1;
    SumUp sumUp2;
    SumUp sumUp3;
    SumUp sumUp4;

    // 定义 tasks
    std::packaged_task<int(int, int)> sumTask1(sumUp1);
    std::packaged_task<int(int, int)> sumTask2(sumUp2);
    std::packaged_task<int(int, int)> sumTask3(sumUp3);
    std::packaged_task<int(int, int)> sumTask4(sumUp4);

    // 获取 futures
    std::future<int> sumResult1 = sumTask1.get_future();
    std::future<int> sumResult2 = sumTask2.get_future();
    std::future<int> sumResult3 = sumTask3.get_future();
    std::future<int> sumResult4 = sumTask4.get_future();

    // 把tasks放进一个deque容器
    std::deque< std::packaged_task<int(int, int)> > allTasks;
    allTasks.push_back(std::move(sumTask1));
    allTasks.push_back(std::move(sumTask2));
    allTasks.push_back(std::move(sumTask3));
    allTasks.push_back(std::move(sumTask4));

    int begin{ 1 };
    int increment{ 2500 };
    int end = begin + increment;

    // 每个任务在独立的线程中执行
    while (!allTasks.empty()) {
        std::packaged_task<int(int, int)> myTask = std::move(allTasks.front());
        allTasks.pop_front();
        std::thread sumThread(std::move(myTask), begin, end);
        begin = end;
        end += increment;
        sumThread.detach();
    }

    // 获取执行结果
    auto sum = sumResult1.get() + sumResult2.get() + sumResult3.get() + sumResult4.get();

    std::cout << "sum of 0 .. 10000 = " << sum << std::endl;

    std::cout << std::endl;

}

程序的工作还是挺简单的。
计算0~10000的和,采用了四个线程,最后在future的协作下将各线程的和相加。
当然你也可用使用高斯公式。

  • 第一步,我把工作包打包在了 std::packaged_task 对象里(第28~31行)。
    工作包是类SumUp的对象实例(第9~16行)。
    工作是在对象的()运算符重载中完成的(第 11-15 行),从beg到end将这些数字相加,最后返回它们的和。
    std::packaged_task (第28~31行)可以处理callable,需要两个整型参数并返回整型数据。

  • 第二步,我必须借助std::packaged_task 对象创建future对象,第34~37行很明确的做了这件事。
    packaged_task实际上在通信通道中是个promise。
    在这些行中我直接显式定义了future:
    std::future sumResult1= sumTask1.get_future();
    当然也可以非显式定义,这样子:
    auto sumResult1= sumTask1.get_future();

  • 第三步,该是实际工作了。
    task包 packaged_task 被放进了一个双端队列std::deque中(第40 ~ 44行)。
    在while循环中,每个 packaged_task 都被执行了(第51-58行)。
    具体过程是:在第52行取出队列头,把它放进了一个新的线程中(第54行),让其在后台运行。
    std::packaged_task对象是被不可复制的,所以我才用了move语义(在第52~54行)。
    所有的promise都有这个特性,对于future和线程也是一样的。
    这条规则有一个例外:std::shared_future..

  • 第四步,也是最后一步,4个future通过get调用获取相加的结果(第61行)。

老实说,std::packaged_task 没有std::async创建起来方便,但是也还算简单啦。

C++异步callable包装器 std::packaged_task_第1张图片


可以做的优化

C++11有一个函数

std::thread_hardware_concurrency()

,它提供了你的系统的cpu核心数量。
如果不可计算时,这个值会返回0的,所以你应该在程序中验证这个值 (第 31 行)。
使用现在的GCC, clang 或 Microsoft 编译器,我都获取到了正确的结果4。
在packagedTask.cpp的基础上,我用了这个核心数,针对我的硬件来动态调整我的软件。
这样,我的硬件就充分利用了。
如下程序:

// packagedTaskHardwareConcurrency.cpp

#include 
#include 
#include 
#include 
#include 
#include 

class SumUp {
public:
    SumUp(int b, int e) : beg(b), end(e) {}
    int operator()() {
        long long int sum{ 0 };
        for (int i = beg; i < end; ++i) sum += i;
        return sum;
    }
private:
    int beg;
    int end;
};

static const unsigned int hwGuess = 4;
static const unsigned int numbers = 10001;

int main() {

    std::cout << std::endl;

    unsigned int hw = std::thread::hardware_concurrency();
    unsigned int hwConcurr = (hw != 0) ? hw : hwGuess;

    // 定义函数对象 functors
    std::vector sumUp;
    for (unsigned int i = 0; i < hwConcurr; ++i) {
        int begin = (i*numbers) / hwConcurr;
        int end = (i + 1)*numbers / hwConcurr;
        sumUp.push_back(SumUp(begin, end));
    }

    // 定义任务 tasks
    std::deque<std::packaged_task<int()>> sumTask;
    for (unsigned int i = 0; i < hwConcurr; ++i) {
        std::packaged_task<int()> SumTask(sumUp[i]);
        sumTask.push_back(std::move(SumTask));
    }

    // 获取 futures
    std::vector< std::future<int>> sumResult;
    for (unsigned int i = 0; i < hwConcurr; ++i) {
        sumResult.push_back(sumTask[i].get_future());
    }

    // 每个任务在独立的线程中执行
    while (!sumTask.empty()) {
        std::packaged_task<int()> myTask = std::move(sumTask.front());
        sumTask.pop_front();
        std::thread sumThread(std::move(myTask));
        sumThread.detach();
    }

    // 获取执行结果
    int sum = 0;
    for (unsigned int i = 0; i < hwConcurr; ++i) {
        sum += sumResult[i].get();
    }

    std::cout << "sum of 0 .. 100000 = " << sum << std::endl;

    std::cout << std::endl;

}

原文地址:

http://www.modernescpp.com/index.php/asynchronous-callable-wrappers

你可能感兴趣的:(Moderns,C++)