4.2 使用future等待一次性事件发生
- 两种future:独占future(unique future,即std::future<>)和共享future(shared future,即std::shared_future<>
#include <future>
#include <iostream>
int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
std::future<int> the_answer=std::async(find_the_answer_to_ltuae);
do_other_stuff();
std::cout<<"The answer is "<<the_answer.get()<<std::endl;
}
- 不过,我们还能够给std::async()补充一个参数,以指定采用哪种运行方式。参数的类型是std::launch,其值可以是std::launch::deferred或std::launch::async[8]。前者指定在当前线程上延后调用任务函数,等到在future上调用了wait()或get(),任务函数才会执行;后者指定必须另外开启专属的线程,在其上运行任务函数。该参数的值还可以是std::launch::deferred | std::launch:: async,表示由std::async()的实现自行选择运行方式。最后这项是参数的默认值。若延后调用任务函数,则任务函数有可能永远不会运行
4.2.2 关联future实例和任务
三种方式其实代表了 C++ 并发编程中,从 “全自动” 到 “全手动” 的三种不同粒度的控制权。它们的核心目的都是为了获取一个 std::future(未来的结果),但**“谁来执行”以及“如何传递结果”**的方式不同。




4.2.4 将异常保存到future中
- 包装的任务函数在执行时抛出异常,则会代替本应求得的结果,被保存到future内并使其准备就绪。只要调用get(),该异常就会被再次抛出。


#include <iostream>
#include <future>
#include <thread>
#include <exception>
void bad_chef(std::promise<int> prom) {
// 厨师拿到订单(promise)
std::cout << "厨师: 我拿到订单了,但我不想做饭..." << std::endl;
// 正常流程应该调用 prom.set_value(100);
// 但是!这里什么都没做,函数结束了。
// prom 是局部变量,函数结束时它会被【销毁/析构】。
}
int main() {
// 1. 创建承诺
std::promise<int> prom;
// 2. 获取未来的票据
std::future<int> f = prom.get_future();
// 3. 开启线程,把 promise 移交给厨师(移动语义)
std::thread t(bad_chef, std::move(prom));
// 4. 顾客等待取餐
try {
std::cout << "顾客: 我在等饭..." << std::endl;
int result = f.get(); // 这里会阻塞等待
std::cout << "顾客: 拿到饭了: " << result << std::endl;
}
catch (const std::future_error& e) {
// 5. 捕获到了 Broken Promise 异常!
std::cout << "\n>>> 顾客生气了: 发生错误! <<<" << std::endl;
std::cout << " 错误代码: " << e.code() << " (broken_promise)" << std::endl;
std::cout << " 原因: 厨师(Promise)销毁前没有给值。" << std::endl;
}
t.join();
return 0;
}

4.2.5 多个线程一起等待
C++ 并发编程中 “单次消费”与“多次广播”




4.3 限时等待
两种超时(timeout)机制可供选用:一是迟延超时(duration-based timeout),线程根据指定的时长而继续等待(如30毫秒);二是绝对超时(absolute timeout),在某特定时间点(time point)来临之前,线程一直等待。大部分等待函数都具有变体,专门处理这两种机制的超时。处理迟延超时的函数变体以“_for”为后缀,而处理绝对超时的函数变体以“_until”为后缀。
4.3.1 时钟类


4.3.2 时长类


- 类型转换
大单位 -> 小单位(自动/隐式): 1 小时 -> 3600 秒。这是绝对安全的,数据不会丢,所以可以自动转。


4.3.3 时间点类



4.4 运用同步操作简化代码
4.4.1 利用future进行函数式编程
list中的splice
它的核心作用是:将元素从一个 list 移动到另一个 list(或同一个 list 的不同位置)。核心优势:零拷贝,极速 (O(1)O(1)O(1))与 std::vector 不同,list 的 splice 操作不会复制或移动数据本身,也不会分配或释放内存。它只是简单地修改了节点之间的指针指向。

std::partition
它的核心作用是:根据你指定的条件(谓词),将容器中的元素“一分为二

#include <iostream>
#include <list>
#include <algorithm>
#include <thread>
#include <future>
template <typename T>
std::list<T> parallel_quick_sort(std::list<T> input)
{
if (input.empty())
{
return input;
}
std::list<T> result;
// 取出第一个元素作为基准
result.splice(result.begin(), input, input.begin());
T const &pivot = *result.begin();
// 根据基准划分元素为两半
auto divide_point = std::partition(input.begin(), input.end(), [&](T const &t)
{ return t < pivot; });
// 将input中小于基准的元素取出放到lower_part中
sdt::list<T> lower_part;
lower_part.splice(lower_part.end(), input, input.begin(), divide_point);
// 开启一个现场基础递归快排小于基准的部分
std::future<std::list<T>> new_lower(std::async(&*parallel_quick_sort<T>, std::move(lower_part)));
// 递归快排大于等于基准的部分
auto new_higher(parallel_quick_sort(std::move(input)));
// 将排好序的小于基准的部分加入结果中的基准的后面
result.splice(result.end(), new_higher);
// 等待小于基准的部分排好序后加入结果中的基准的前面
result.splice(result.begin(), new_lower.get());
// 这里的get()会阻塞直到new_lower完成
return result;
}

四个概念纠清




4.4.2 使用消息传递进行同步
std::experimental
C++并发中future与同步操作详解
878

被折叠的 条评论
为什么被折叠?



