概述
下面一个示例说明co_await 的用法。
template<class T>
struct generator
{
struct promise_type
{
std::suspend_never initial_suspend() const noexcept { std::cout<<"initial_suspend"<<std::endl; return {}; }
std::suspend_never final_suspend() const noexcept { std::cout<<"final_suspend"<<std::endl; return {}; }
void unhandled_exception() noexcept { }
generator<T> get_return_object() noexcept
{
std::cout<<"get_return_object"<<std::endl; return {};
}
auto await_transform(T id)
{
return await_op {id};
}
struct await_op
{
bool await_ready() const noexcept
{
std::cout<<"await_ready"<<std::endl;
return _id%2;
}
void await_suspend(std::coroutine_handle<> cont) const
{
std::cout<<"await_suspend"<<std::endl;
}
T await_resume()
{
std::cout<<"await_resume"<<std::endl;
return _id+1;
}
T _id;
};
};
};
generator<int> co_fun2()
{
std::cout << "============co_fun2"<<std::endl;
int id=co_await 1;
std::cout << "co_await:"<<id<<std::endl;
}
generator<int> co_fun3()
{
std::cout << "============co_fun3"<<std::endl;
int id=co_await 2;
std::cout << "co_await:"<<id<<std::endl;
}
int main()
{
co_fun2();
std::cout<<std::endl;
co_fun3();
std::cout<<"end of main"<<std::endl;
}
输出如下:
get_return_object
initial_suspend
============co_fun2
await_ready
await_resume
co_await:2
final_suspend
get_return_object
initial_suspend
============co_fun3
await_ready
await_suspend
end of main
上例中,编写了一个满足awaitabble接口的await_op结构,由await_ transform返回,这是构造co_await expr的一种方法(另外一种见下例)。主要由于co_await的输入不同,await_ready的返回也不同,co_fun2, co_fun3的执行顺序如下图所示。
下面是co_await另一个有些实用意义的示例。
template<typename T>
void println(const char *title, T content)
{
static std::mutex mtx;
const std::lock_guard<std::mutex> lock(mtx);
std::time_t tm = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::cout<<"["<<tm<<","<<std::this_thread::get_id()<<"]"<<title<<" "<<content<<std::endl;
}
//==============================================================================
template <typename T, typename... Args>
requires(!std::is_void_v<T> && !std::is_reference_v<T>)
struct std::coroutine_traits<std::future<T>, Args...>
{
struct promise_type:std::promise<T>
{
std::suspend_never initial_suspend() const noexcept { println("initial_suspend",""); return {}; }
std::suspend_never final_suspend() const noexcept { println("final_suspend",""); return {}; }
void unhandled_exception() noexcept { this->set_exception(std::current_exception()); }
std::future<T> get_return_object() noexcept
{
println("get_return_object","");
return this->get_future();
}
void return_value(const T &value) noexcept(std::is_nothrow_copy_constructible_v<T>)
{
this->set_value(value);
}
void return_value(T &&value) noexcept(std::is_nothrow_move_constructible_v<T>)
{
println("return_value","");
this->set_value(std::move(value));
}
};
};
using namespace std::chrono_literals;
template <typename T>
auto operator co_await(std::future<T> future) noexcept
requires(!std::is_reference_v<T>)
{
struct awaiter : std::future<T>
{
bool await_ready() const noexcept
{
println("await_ready","");
return this->wait_for(0s) != std::future_status::timeout;
}
void await_suspend(std::coroutine_handle<> cont) const
{
println("await_suspend","");
std::thread([this, cont] {
println("wait for worker","");
this->wait();
println("worker OK","");
cont.resume();
}).detach();
}
T await_resume()
{
println("await_resume","");
return this->get();
}
};
return awaiter{std::move(future)};
}
std::future<int> co_fun4(float a, float b)
{
println("co_fun4","");
int r=2;
float f = co_await std::async(std::launch::async,[a](){
println("async start","");
std::this_thread::sleep_for(1s);
println("end of sleep","");
return a*2.0;
});
println("end of co_await",f*r);
co_return (int)(f*b);
}
int main()
{
println("main","");
auto res2= co_fun4(2.1,2.0);
println(" wait for co_fun4 return:","");
int i=res2.get();
println("end of main",i);
}
输出如下:
[1608685871,139856935995200]main
[1608685871,139856935995200]get_return_object
[1608685871,139856935995200]initial_suspend
[1608685871,139856935995200]co_fun4
[1608685871,139856935995200]await_ready
[1608685871,139856935995200]await_suspend
[1608685871,139856935995200] wait for co_fun4 return:
[1608685871,139856918120192]async start
[1608685871,139856909727488]wait for worker
[1608685872,139856918120192]end of sleep
[1608685872,139856909727488]worker OK
[1608685872,139856909727488]await_resume
[1608685872,139856909727488]end of co_await 8.4
[1608685872,139856909727488]return_value
[1608685872,139856909727488]final_suspend
[1608685872,139856935995200]end of main 8
上面的输出有点“混乱”,其实上面程序中产生了3个线程,一个主线程main,一个由std::async产生的工作线程(称为worker),一个在await_suspend中产生的等待-通知线程(称为notifier)。运行示意如下图所示。
在co_await以前,只有main在运行,因而输出到“============co_fun4”。进入co_await,costd::async 产生了线程worker,main依然在运行,wait_ready结果为false,进入await_suspend,产生线程notifier,main继续运行,因没有resume,跳出协程函数,返回到调用者,直到future.get产生阻塞为止。Notifier线程中future等待worker运行完成,如果worker完成,coroutine_handle恢复,调用await_resume(此后的协程恢复部分都是在notifier中运行),co_await返回得到结果,结果再由co_return返回。
注意协程resume后,协程输入参数b及局部变量r都得到恢复,说明它们之前都得到了暂存处理。
编写协程的目的是降低异步操作的难度,使它们看起来像是同步操作,因而调用者运行在一个线程,异步操作通常在另一个线程,但通过future可将结果安全地带回调用者线程(future自带同步机制),我们编写的部分(调用者线程)没有数据竞争,也不需要锁。
本文未涉及协程函数发生异常的情况,发生异常时,unhandled_exception将被调用,请参阅有关文档。
从上面的示例,可以看出目前写一个coroutine还比较麻烦,协程函数“隐含的规则”比较多,隐藏了我们看不到的逻辑,coroutine的风格似乎与以前C/C++严谨、“可控”的风格有点不同。但不管如何,C++的coroutine会改变我们的一些想法。coroutine的出现,同时会影响到现有第三方库中异步操作的实现,不知道它们会不会提供协程的方式?偶然看到C++23中将coroutine的使用列为一项重要内容,看来甜度更大的语法糖已经在路上,它们如何简化现有的写法,使之更便捷,我们还是拭目以待吧。
最后
以上就是潇洒八宝粥为你收集整理的C++20新特性—coroutine协程(二)的全部内容,希望文章能够帮你解决C++20新特性—coroutine协程(二)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复