C++ 异步编程探索(一) task链式调度 -- future CONTINUATION

16 篇文章 4 订阅

同步Vs异步编程, 一直是大家讨论的热点。 同步编程以其逻辑简单,受到了一些对时间不敏感的程序青睐,而异步编程以其高效深受处女座程序员喜爱。

作为一只处女座程序猿,当然要异步异步异步。。。

说到异步, C++11之前, 用的最多的就是各种callback function。举个例子, 假如你是一个老板,告诉自己的秘书把文件打印出来,打完了送给我。然后你就可以干别的活,等打印好了,秘书会到你的办公室给你文件,你就可以继续处理文件。

在这里打印文件就是我们设置的callback function。 等文件打印完成这个事件发生,秘书就会带着文件给你。也就是调用你的callback function。秘书可能就是你新启动的线程。

同步编程就是你自己去打印文件,等文件打印完了再干其它的事情。

显然异步编程更有效率,但是现在有几个问题:

问题一 : 秘书怎么把打印完的文件交给你。可能现实中很简单,但是在编程中就涉及到线程之间通信,线程间通知机制,这个以后在说。

问题二 : 人类的思维是同步的,连续的,由于引入太多的callback function。这会打断人类的思维。

下面就讨论第二个问题。

第一种解决方法是最近很火的名词“协程”当然不是那个携程。GO语言把这个概念带火了,它的思想就是线程的调度开销太大,所以在线程里又起了很多轻量级的“线程”,也就是“协程”。线程自己调度,无非是存下当前的各种寄存器信息后执行其它程序,完事后在将之前存的东西在还原,继续执行, 但是C语言并不像GO语言这种天生自带“携程”,确实有很多框架比如boost::croroutine微信也开源了一套。 有了携程就可以按照人类的思维,一步一步的执行想干的活,协程的痛点是如何处理“阻塞”程序,比如socket,I/O等等。对于协程,还是持观望态度。
下面写一个小例子,看看封装后的协程写异步程序是多么爽的一件事,至于为什么爽是因为同步编程才是符合人类的思维习惯的。以前设置异步读取操作后,数据的处理都必须在回调函数中处理,现在可以直接在异步操作后接着处理啦!

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <string>
#include <ctime>
#include <iostream>
#include <boost/enable_shared_from_this.hpp>

using namespace boost::asio;
using std::cerr;
using std::endl;

io_service io_service_;

class session : public boost::enable_shared_from_this<session>
{
  public:
    explicit session(ip::tcp::socket socket) : sock_(std::move(socket)),
                                               strand_(io_service_),
                                               uuid_(std::rand())
    {
    }

    ~session() { cerr << "~sessoin ->" << uuid_ << endl; }

    void go()
    {
        auto self(shared_from_this());
        boost::asio::spawn(strand_,
                           boost::bind(&session::do_echo, self, _1));
    }

    void do_echo(yield_context yield)
    {
        char data[128];
        std::size_t n = sock_.async_read_some(boost::asio::buffer(data), yield);
        cerr << "RECVED:【" << data << "】->" << uuid_ << endl;
        std::time_t now = std::time(nullptr);
        std::string time_str = std::ctime(&now);
        async_write(sock_, buffer(time_str), yield);
        sock_.shutdown(ip::tcp::socket::shutdown_send);
    }

  private:
    ip::tcp::socket sock_;
    io_service::strand strand_;
    std::size_t uuid_;
};

void start_accept(yield_context yield)
{
    ip::tcp::acceptor acceptor(io_service_, ip::tcp::endpoint(ip::tcp::v4(), 2016));

    for (;;)
    {
        boost::system::error_code ec;
        ip::tcp::socket socket(io_service_);

        acceptor.async_accept(socket, yield[ec]);
        if (!ec)
            boost::make_shared<session>(std::move(socket))->go();
    }
}

int main(int argc, char *argv[])
{
    boost::asio::spawn(io_service_, start_accept);
    io_service_.run();
}

编译命令g++ -std=c++11 test.cpp -lboost_system -lboost_coroutine -lboost_context -o test
其实,感觉现实中协程更多的是对编程方式的改变,对控制流的操控可以用同步的结构写出异步的效果,但是协程是用户态的而不是原生的多线程,所以并不能并行执行提高并发率。但是协程能够在各个协程间进行高效的切换,这一点可以做到比传统依赖于异步调度的效率更高,这才体现出协作的本质吧!
第二种:
C++11中引入了一个新的东东 std::future,有个这个就可以让你设置一个工作,然后在需要结果时调用get()拿到你想要的值。

前面说了,人类的思维是连续的,同步的。所以人类更适应连续的链式调用,比如告诉秘书打印一份文件然后把文件交给XXX。

也就是future .then()

可是令人失望的是future.then()作为候选并没有进入C++17,而是进入了std::experimental。

https://en.cppreference.com/w/cpp/experimental/future/then

不过还好我们有boost这个神器。

下面我们尝试一下:

#include <iostream>
#include <string>
#define BOOST_THREAD_VERSION 4

#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION

#include <boost/thread/future.hpp>
#include <stdio.h>

using namespace boost;

int main()
{
    future<int> f1 = async([]() { return 123; });

    future<std::string> f2 = f1.then([](future<int> f) {
                                   std::cout << f.get() << std::endl; // here .get() won't block
                                   return std::string("sgfsdfs");
                               })
                                 .then([](future<std::string> f) {
                                     std::cout << f.get() << std::endl;
                                     return std::string("sgfsdfs");
                                 })
                                 .then([](future<std::string> f) {
                                     std::cout << f.get() << std::endl;
                                     return std::string("sgfsdfs");
                                 });
    getchar();
}

这里要吐槽一下, 首先编译命令g++ then.cpp -std=c++11 -lboost_system -lboost_thread官方网站只是写了一些例子,完全不能编译, 首先你要设置版本信息,要不然编译不过#define BOOST_THREAD_VERSION 4应该2以上就行,还要定义一些宏#define BOOST_THREAD_PROVIDES_FUTURE
要用then还要定义下边的宏
#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION

编译问题解决后,是不是很爽?完全不用考虑是哪个线程干的工作, 只要then then then,活就干完了。

通过GDB 看了一下运行流程,发现每个then都会起一个新的线程。 首先起线程很耗资源。其次还有个线程安全的问题。所以这种task适合task之间没有依赖的工作。

为了解决这个问题很多大厂已经有相应的解决方案,例如微软的PPL
https://msdn.microsoft.com/en-us/library/dd492418.aspx
在linux下阉割版pplx
https://github.com/Microsoft/cpprestsdk/wiki/Programming-with-Tasks
intel有TBB
https://www.threadingbuildingblocks.org/tutorial-intel-tbb-task-based-programming

他们都有完善的task schedule 功能。但是都不是标准的实现。

现在我的想法是实现和std::promise&&std::future类似的功能,future有then也就是有continuation功能。
同时为了解决线程间数数据问题,将scheule功能用boost::asio实现,所有task都schedule到一个thread(后续到多个thread类似boost::srand)

有机会实现这个想法。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值