`C++并发在Action`第4章疑惑

问题描述 投票:0回答:1

我目前正在阅读

C++ Concurrency in Action 2nd edition
,第 111 页(第 4 章)。这是一个有点长的问题。

因此,作者展示了如何编写一个处理用户登录的函数的良好并发版本。 这是第一个连续片段:

void process_login(std::string const& username,std::string const& password)
{
 try {
 user_id const id=backend.authenticate_user(username,password);
 user_data const info_to_display=backend.request_current_info(id);
 update_display(info_to_display);
 } catch(std::exception& e){
 display_error(e);
 }
}

[1]第一个尝试是使用

std::async
,这样整个过程都在一个新的线程中完成,如:

std::future<void> process_login(
 std::string const& username,std::string const& password)
{
 return std::async(std::launch::async,[=](){
 try {
 user_id const id=backend.authenticate_user(username,password);
 user_data const info_to_display=
 backend.request_current_info(id);
 update_display(info_to_display);
 } catch(std::exception& e){
 display_error(e);
 }
 });
}

但是,根据作者的说法,[1] 不太好,因为“使用普通的 std::async,您可以将其全部转入后台线程,如下一个清单,但这仍然会阻塞该线程,消耗资源在等待任务完成时

[2] 因此,作者展示了一种新的处理方法,通过使用延续和链接任务:

std::experimental::future<void> process_login(
 std::string const& username,std::string const& password)
{
 return spawn_async([=](){
 return backend.authenticate_user(username,password);
 }).then([](std::experimental::future<user_id> id){
 return backend.request_current_info(id.get());
 }).then([](std::experimental::future<user_data> info_to_display){
 try{
 update_display(info_to_display.get());
 } catch(std::exception& e){
 display_error(e);
 }
 });
}

其中

spawn_async
是:

template<typename Func>
std::experimental::future<decltype(std::declval<Func>()())>
spawn_async(Func&& func){
 std::experimental::promise<
 decltype(std::declval<Func>()())> p;
 auto res=p.get_future();
 std::thread t(
 [p=std::move(p),f=std::decay_t<Func>(func)]()
 mutable{
 try{
 p.set_value_at_thread_exit(f());
 } catch(...){
 p.set_exception_at_thread_exit(std::current_exception());
 }
 });
 t.detach();
 return res;
}

但是,根据作者的说法,这仍然很糟糕,因为“如果函数在内部调用后端块,因为它们正在等待 消息通过网络或完成数据库操作,那么您 还没做完。您可能已将任务拆分为各个部分,但它们仍然是 阻塞调用,因此您仍然会遇到阻塞线程。您需要的是后端调用 返回当数据准备好时准备好的 future,而不阻塞任何 线程

[3] 所以最后一段代码是:

std::experimental::future<void> process_login(
 std::string const& username,std::string const& password)
{
 return backend.async_authenticate_user(username,password).then(
 [](std::experimental::future<user_id> id){
 return backend.async_request_current_info(id.get());
 }).then([](std::experimental::future<user_data> info_to_display){
 try{
 update_display(info_to_display.get());
 } catch(std::exception& e){
 display_error(e);
 }
 });
}

其中

backend
函数返回
experimental::future

所以这就是所有的疑问。

  • 我不明白[1]和[2]片段之间的区别是什么。当然,[1] 单个任务(例如
    backend.request_current_info
    )可能会阻塞,但在 [2] 中,同一个任务也可能会阻塞,然后 then() 仅当之前的任务结束时才会继续。
  • 考虑到
    future
    .then()
    函数仅在前一个任务完成后才继续执行,那么线程在最后一个片段 [3] 中到底如何不被阻塞?
c++ multithreading performance concurrency
1个回答
0
投票

我也无法理解第一个问题,尤其是作者后来承认:

“如果函数在内部调用后端块,因为它们正在等待消息穿过网络或等待数据库操作完成,那么您还没有完成。”

对于第二个问题,也许我已经明白了。

我认为作者的意思是

authenticate_user
就像一个
while
循环,不断检查网络响应是否已到来,而
async_authenticate_user
只是将
promise
对象传递给某个网络框架,该框架会在每一帧上打勾当等待的响应到达时,致电
promise.set_value
。所以后面的实现不会再阻塞线程了。

© www.soinside.com 2019 - 2024. All rights reserved.