2022-08-06 C++并发编程(九)


前言

我们设计程序时,有时会出现这种状况,一条主线,一条支线,两条线分别做不同的事情,最后合并为一支。

我们可以用thread开启线程,但如果需要回传结果,稍显麻烦,此时可以用 std::async() 函数和 std::future 对象解决。

分离线程功能,还可用任务包 std::packaged_task 在线程间传递任务,由多个线程发起传递任务,由固定功能线程接收线程包,执行任务。


一、从后台任务返回值

通过 std::async() 函数将与主线无关的部分分离,在恰当的时间返回结果,很容易实现程序的异步多支线逻辑。

#include <future>
#include <iostream>

auto findTheAnswerToLtuae(int i) -> int
{
    return i * i;
}

void doOtherStuff()
{
    std::cout << "do something." << std::endl;
}

auto main() -> int
{
    std::future<int> theAnswer = std::async(findTheAnswerToLtuae, 5);
    doOtherStuff();
    std::cout << "the answer is " << theAnswer.get() << std::endl;
    return 0;
}

std::async() 函数的调用方法,可以传入普通的函数指针,及后续实参,如果是类的成员函数,可以传入成员函数指针,类对象指针(或类对象本身,或std::ref()包装的对象),及后续实参。

#include <future>
#include <iostream>
#include <string>

struct X
{
    void foo(int i, const std::string &str)
    {
        std::cout << i << str << std::endl;
    }
    auto bar(const std::string &str) -> std::string
    {
        std::cout << str << std::endl;
        return str;
    }
};

struct Y
{
    auto operator()(double d) -> double
    {
        std::cout << d << std::endl;
        return d;
    }
};

struct moveOnly
{
    moveOnly() = default;
    moveOnly(moveOnly && /*unused*/) noexcept
    {}
    moveOnly(const moveOnly &) = delete;
    auto operator=(moveOnly && /*unused*/) noexcept -> moveOnly &
    {
        return *this;
    }
    auto operator=(const moveOnly &) -> moveOnly & = delete;
    void operator()()
    {
        std::cout << "moveOnly." << std::endl;
    }
};

auto baz(X &x) -> X
{
    return x;
}

auto main() -> int
{
    X x;
    //调用成员函数和 std::thread 实例的调用方法相同
    //传入成员函数指针,对象指针,函数实参
    auto f1 = std::async(&X::foo, &x, 42, "hello");
    //直接传入对象,则会构造对象副本,不影响原对象
    auto f2 = std::async(&X::bar, x, "goodbye");

    Y y;
    //调用仿函数
    auto f3 = std::async(Y(), 3.14);
    //通过传入可执行对象的引用调用仿函数
    auto f4 = std::async(std::ref(y), 2.718);

    //调用普通函数,引用类型的参数需std::ref()函数进行传入
    auto f5 = std::async(baz, std::ref(x));

    //调用仿函数
    auto f6 = std::async(moveOnly());

    //强制新线程调用仿函数
    auto f7 = std::async(std::launch::async, Y(), 1.2);

    //强制本线程递延调用函数指针
    auto f8 = std::async(std::launch::deferred, baz, std::ref(x));

    //由编译器自行选择是否开启新线程或本线程递延调用
    auto f9 = std::async(std::launch::deferred | std::launch::async, baz,
                         std::ref(x));

    //此时开始执行
    f8.wait();

    return 0;
}

二、线程间传递任务

多线程程序设计中,我们可以将某些线程设计为专注于某些功能,其他线程通过消息传递给这些功能线程,完成特定功能。

例如GUI框架,由专门的界面更新线程,负责界面更新,用 std::packaged_task 对象实现。

通过线程任务发送函数 postTaskForGuiThread,将预执行任务加入队列 tasksQue,界面更新线程函数 guiThread 轮询队列tasksQue,如果有任务,则执行。

像这种分离线程功能的设计,用 std::packaged_task 配合队列,可较为简单的得以实现。

#include <deque>
#include <future>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <utility>
#include <vector>

std::mutex m;
bool shutDown;

//任务队列
std::deque<std::packaged_task<void()>> tasksQue;

auto guiShutdownMessageReceived() -> bool
{
    return shutDown;
}

void getAndProcessGuiMessage()
{
    std::cout << "process gui message!" << std::endl;
}

// gui运行线程
void guiThread()
{
    //判断是否程序结束
    while (!guiShutdownMessageReceived())
    {
        //取得gui信息,执行
        getAndProcessGuiMessage();

        //任务包
        std::packaged_task<void()> task;

        {
            std::lock_guard<std::mutex> lk(m);
            //任务队列为空则继续轮询
            if (tasksQue.empty())
            {
                continue;
            }
            //取得任务
            task = std::move(tasksQue.front());
            tasksQue.pop_front();
        }
        //运行任务
        task();
    }
}

// gui后台线程
std::thread guiBgThread(guiThread);

int cnt = 0;
//测试
void test()
{
    std::lock_guard<std::mutex> lk(m);
    std::cout << "test" << cnt++ << std::endl;
    if (cnt == 3)
    {
        shutDown = true;
    }
}

//发送gui线程任务
template <typename Func>
auto postTaskForGuiThread(Func f) -> std::future<void>
{
    //打包函数到任务
    std::packaged_task<void()> task(f);
    //取得任务future
    std::future<void> resultFuture = task.get_future();
    std::lock_guard<std::mutex> lk(m);
    //将任务传递给任务队列
    tasksQue.push_back(std::move(task));
    //返回任务future
    return resultFuture;
}

auto main() -> int
{
    std::thread test1(postTaskForGuiThread<void()>, test);
    std::thread test2(postTaskForGuiThread<void()>, test);
    std::thread test3(postTaskForGuiThread<void()>, test);

    test1.join();
    test2.join();
    test3.join();

    guiBgThread.join();

    return 0;
}


总结

当我们需要分离线程功能,可以通过标准库 std::async() 函数实现,也可通过多个线程进行任务包装 std::packaged_task, 用固定线程接收任务包,执行任务,实现稍复杂的线程功能分离逻辑。

参考文献:C++并发编程实战(第2版)[英] 安东尼•威廉姆斯(Anthony Williams)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不停感叹的老林_<C 语言编程核心突破>

不打赏的人, 看完也学不会.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值