C++ 的标准异步库 std::async

作者: , 共 3700 字 , 共阅读 0

C++11 的标准异步库至少包含下面内容:

  • std::promise
  • std::future
  • std::packaged_task
  • std::async

它们的作用要从std::thread的一个示例说起:

int a = 1;
std::thread thread([a](int b) {
    return a + b;
}, 2);

thread.join();

上面这段代码会新创建一个线程来计算1 + 2。但我们没办法获取线程的计算返回值。异步库则封装了新建线程执行任务,同时获取任务返回值的操作。比如可以使用std::promisestd::future

int a = 1;
std::promise<int> res;
std::future<int> future = res.get_future();

std::thread thread([a](int b, std::promise<int> res) {
    res.set_value(a + b);
}, 2, res);

std::cout << future.get() << std::endl;

也可以使用std::packaged_taskstd::future

int a = 1;
std::packaged_task<int(int)> task = [a](int b) {
    return a + b;
};
std::future<int> future = task.get_future();
task(2);
std::cout << future.get() << std::endl; // 2

而使用std::async可以大大简化上面的工作:

std::future<int> future3 = std::async([a](int b) {
    return a + b;
}, 2);

std::future<int> future4 = std::async([a](int b) {
    return a + b;
}, 3);

std::cout << future3.get() + future4.get() << std::endl; // 7

std::async的源代码位于https://gcc.gnu.org/onlinedocs/gcc-7.5.0/libstdc++/api/a00074_source.html,其函数实现摘抄如下:

template<typename _Fn, typename... _Args>
std::future<__async_result_of<_Fn, _Args...>> async(launch __policy, _Fn&& __fn, _Args&&... __args) {
    std::shared_ptr<__future_base::_State_base> __state;

    auto caller = std::thread::__make_invoker(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...);
    if ((__policy & launch::async) == launch::async) {
        __try {
            __state = std::make_shared<__future_base::_Async_state_impl>(std::move(caller));
        }
        catch(const system_error& __e) {
            if (__e.code() != errc::resource_unavailable_try_again || (__policy & launch::deferred) != launch::deferred)
                throw;
        }
    }
    if (!__state) {
        __state = std::make_shared<__future_base::_Deferred_state>(std::move(caller));
    }
    return future<__async_result_of<_Fn, _Args...>>(__state);
}

如果lanch policystd::launch::async,则在下面构造类中启动线程:

template<typename _BoundFn, typename _Res>
class __future_base::_Async_state_impl final : public __future_base::_Async_state_commonV2 {
public:
    explicit _Async_state_impl(_BoundFn&& __fn) 
        : _M_result(new _Result<_Res>()), _M_fn(std::move(__fn))
    {
        _M_thread = std::thread{ [this] {
            __try {
                _M_set_result(_S_task_setter(_M_result, _M_fn));
            }
            __catch (const __cxxabiv1::__forced_unwind&) {
                // make the shared state ready on thread cancellation
                if (static_cast<bool>(_M_result))
                    this->_M_break_promise(std::move(_M_result));
                    __throw_exception_again;
                }
            } 
        };
    }

    ~_Async_state_impl() { if (_M_thread.joinable()) _M_thread.join(); }

private:
    _Ptr_type _M_result;
    _BoundFn _M_fn;
}

如果有异常或者lanch policystd::launch::deferred,不会启动线程,只是保存caller待用:

template<typename _BoundFn, typename _Res>
class __future_base::_Deferred_state final : public __future_base::_State_base
{
public:
    explicit _Deferred_state(_BoundFn&& __fn) : _M_result(new _Result<_Res>()), _M_fn(std::move(__fn))
    { }

private:
    typedef __future_base::_Ptr<_Result<_Res>> _Ptr_type;
    _Ptr_type _M_result;
    _BoundFn _M_fn;

    virtual void _M_complete_async() {
        _M_set_result(_S_task_setter(_M_result, _M_fn), true);
    }

    virtual bool _M_is_deferred_future() const { return true; }
};

【未完待续】

Q. E. D.

类似文章:
编程 » C++, 智能指针
前面已经提到std::shared_ptr有三个缺陷:
std::thread是 C++ 11 新引入的标准线程库。在同样是 C++ 11 新引入的 lambda 函数的辅助下,std::thread用起来特别方便:
智能指针在现代 C++里用得越多。以前只知道它大致的原理,比如使用引用计数。但很多实现细节并不清楚,最直接的,它是如何实现多线程安全的?今天找了 gnu c++ library 的实现好好看了一下。
编程 » C++, 智能指针
理论上而言,当 C++提供了std::unique_ptr, C++的程序就不应该出现普通指针了。所有普通指针都可以用std::unique_ptr代替,避免手动删除对象。
编程 » C++, C++标准库
std::tuple的原理并不复杂,但有些细节非常有意思。其中有一个是至少在gnu C++ std的实现中,std::tuple是倒序存储的:
编程 » C++, Boost, 智能指针
如果理解了侵入式容器,侵入式智能指针也很容易理解。传统的智能指针std::shared_ptr使用了和数据无关的引用计数,这带来两个问题:
看到网上有片段,提到没有必要自己实现自旋锁,因为标准库的 std::mutex 和现在的自旋锁的实现没有两样。比较好奇,翻了一些资料,试图找到答案。
编程 » C++, 编译错误
在 gcc 中,存在继承关系的模版类,子类无法直接访问父类的成员,即使该成员是protectedpublic
由 Facebook 开发和维护的 C++库 Folly 提供了自旋锁的实现folly::MicroSpinLock,代码文件地址:https://github.com/facebook/folly/blob/master/folly/synchronization/MicroSpinLock.h
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::small_vector,代码文件地址:https://github.com/facebook/folly/blob/master/folly/small_vector.h
编程 » C++, 智能指针
前面已经提到std::shared_ptr有三个缺陷:
最近在配置 matrix synapse 时,才注意到现在配置一个 https 网站已经非常简单,而且 nginx 也非常好用。