C++多线程:std::async阻塞、共享状态检查与future销毁问题
首先贴出你用于实现旅行商问题的多线程递归代码:
typedef std::pair<std::vector<char>, int> PATH_PAIR; std::list<PATH_PAIR> TSP::return_all_paths_list( PATH_PAIR parent_path_pair, std::list<char> unvisited_cities ) { std::list<PATH_PAIR> subseq_paths_list; std::vector<std::future<std::list<PATH_PAIR>>> future_vec; if (unvisited_cities.empty()) { // END RECURSION // NON-REALTED CODE return subseq_paths_list; } for (const char curr_city: unvisited_cities) { // DO RECURSION PATH_PAIR new_path_pair_addr = parent_path_pair; std::list<char> subseq_unvisited_cities = unvisited_cities; std::future<std::list<PATH_PAIR>> returned_path_list_future; // NON-RELATED CODE returned_path_list_future = std::move(std::async(std::launch::async, return_all_paths_list, this, new_path_pair_addr, subseq_unvisited_cities)); future_vec.push_back(returned_path_list_future); } while (!future_vec.empty()) { // GET-VALUES-WHILE for (std::future<std::list<PATH_PAIR>>& curr_future : future_vec) // GET-VALUES-FOR if (curr_future.valid()) { subseq_paths_list.merge(curr_future.get()); } // future_list.remove_if( // [] (std::future<std::list<PATH_PAIR>> input_future) -> bool { // return !input_future.valid(); // } // ); } return subseq_paths_list; }
1. 关于std::async的析构阻塞问题
a. "move from(移动)"和"bound to a reference(绑定到引用)"的含义
- move from(移动):通过
std::move()将std::async返回的临时std::future对象的所有权转移给另一个std::future变量。移动后原临时对象不再持有共享状态,其析构时不会触发阻塞。 - bound to a reference(绑定到引用):将
std::async返回的临时std::future绑定到右值引用(如std::future<T>&&),本质是避免临时对象在当前语句结束时直接析构,核心目的和移动语义一致——不让未被保存的临时std::future触发阻塞。
b. 使用std::move包裹std::async调用能否避免递归后续迭代阻塞?
可以。你代码中returned_path_list_future = std::move(std::async(...));的写法,将std::async返回的临时std::future所有权转移给了变量,随后又被移动到future_vec中,全程没有临时std::future在迭代过程中析构,因此不会触发后续迭代的阻塞。即使不显式写std::move,直接赋值给变量也能转移所有权,但显式移动的写法更清晰。
c. 示例中的阻塞是否仅因lambda表达式导致?
不是。你注释掉的remove_if lambda和阻塞问题无关。如果出现阻塞,大概率是某处std::async返回的临时std::future未被移动或保存,直接触发了析构阻塞。当前代码的写法不会因lambda导致阻塞,注释的lambda只是后续尝试清理容器的代码,和前置的阻塞场景无关。
2. 关于共享状态与std::future的疑问
a. 如何非阻塞检查共享状态是否ready?
使用std::future::wait_for()或std::future::wait_until()方法,传入零时长超时,通过返回值判断状态:
- 返回
std::future_status::ready:共享状态已就绪; - 返回
std::future_status::timeout:共享状态未就绪; - 由于你用的是
std::launch::async,不会出现std::future_status::deferred的情况。
示例代码:
if (curr_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { subseq_paths_list.merge(curr_future.get()); }
b. 调用get()后如何正确销毁对应的std::future对象?
调用get()后,std::future的valid()会返回false,因为共享状态已被消耗。由于std::future不可拷贝,不能直接用remove_if,需用erase-remove惯用法配合引用传递的lambda:
future_vec.erase( std::remove_if(future_vec.begin(), future_vec.end(), [] (const std::future<std::list<PATH_PAIR>>& f) { return !f.valid(); } ), future_vec.end() );
你之前的编译错误是因为lambda用了值传递(std::future不可拷贝),改成引用传递即可解决。调用get()后容器大小不变,是因为你仅消耗了共享状态但未从容器中移除对应的std::future元素,执行上述erase-remove操作就能删除无效对象、更新容器大小。
内容的提问来源于stack exchange,提问作者nevermind

