织梦做中英文网站,seo两个域名一个网站有影响,自己的网站怎么接广告,怎么建设一个淘宝客网站C11的future和async等关键字 1.async和future的概念
std::async 和 std::future 是 C11 引入的标准库功能#xff0c;用于实现异步编程#xff0c;使得在多线程环境中更容易处理并行任务。它们可以帮助你在不同线程中执行函数#xff0c;并且能够方便地获取函数的结果。
在…C11的future和async等关键字 1.async和future的概念
std::async 和 std::future 是 C11 引入的标准库功能用于实现异步编程使得在多线程环境中更容易处理并行任务。它们可以帮助你在不同线程中执行函数并且能够方便地获取函数的结果。
在之前使用线程的时候我们没有办法很好的获取到线程所执行函数的返回值。甚至更多时候我们使用线程执行的都是不关心返回值的函数。如果真的想要获取线程函数的返回值可以将一个指针作为输出型参数放入线程所执行的函数中。主执行流执行t.join()等待线程执行结束并获取到这个返回值。
但这样并不是非常方便。于是C11就引入了如上两个关键字来帮助我们获取到线程所执行函数的返回值。适用于异步执行某些耗时的函数提高程序运行的效率
异步执行耗时函数主执行流干其他事情通过std::future获取到返回值继续向后执行
基本的并行概念在多线程部分都已经讲过了这里就不多bb直接上代码吧
2.使用
2.1 std::launch
在使用std::async之前还需要认识一个枚举类型 launch在使用std::async的函数传参的时候会用到这里先说一下std::async是用来帮我们创建线程的
enum class launch; // std::launch在 cplusplus网站上有这个枚举类型的释义这里面只有俩值 说一下这俩值的区别
launch::async立即创建一个线程来执行目标函数launch::deferred不立即创建线程而是等待调用std::future的get()函数时才调用这个get函数是用来获取返回值的
好了知道这个就够了哈
2.2 std::result_of
这里还出现了另外一个关键字就顺带也说说是干嘛的其实我自己也不知道现学现卖
#include type_traits // 头文件template class Fn
struct result_of;template class Fn, class... ArgTypes
struct result_ofFn(ArgTypes...);以下是 std::result_of 的基本用法和概念
使用 std::result_of 获取函数调用的返回类型 你可以通过将函数类型和参数类型传递给 std::result_of 来推导函数调用的返回类型。这使得你可以在编译时获取函数调用的结果类型而不需要手动指定它。用法示例 假设有一个函数 int add(int a, int b)你可以使用 std::result_of 来获取该函数在给定参数下的返回类型。
#include iostream
#include type_traitsint add(int a, int b)
{return a b;
}int main()
{std::result_ofdecltype(add) (int, int)::type result add(3, 5);std::cout Result: result std::endl;return 0;
}运行结果如下
$ g test.cpp -o test -stdc11
$ ./test
Result: 8需要注意的是使用decltype关键字来指定函数指针的时候函数名和函数参数之间需要加上否则无法正确推导类型
decltype(add) (int, int) // 正确
decltype(add) (int, int) // 错误 2.3 async
先来看看async函数的样本第一个情况是不显示传入 std::launch第二个函数重载是传入了std::launch作为启动策略
#include future // 头文件// unspecified policy (1)
template class Fn, class... Argsfuturetypename result_ofFn(Args...)::typeasync (Fn fn, Args... args);
// specific policy (2)
template class Fn, class... Argsfuturetypename result_ofFn(Args...)::typeasync (launch policy, Fn fn, Args... args);在cplusplus网站上说到了第一种情况是由编译器自主决定到底是采用 std::launch::async 或 std::launch::deferred这就需要根据平台和编译器实现以及调用逻辑的不同来具体分析了。所以不建议使用第一个还是直接指定launch policy翻译过来是启动策略的会好一点。
所以只看第二个
template class Fn, class... Argsfuturetypename result_ofFn(Args...)::typeasync (launch policy, Fn fn, Args... args);这里采用了可变模板参数来接收多个函数参数类似于可变参数列表。这里还使用了typename关键字来告知编译器result_ofFn(Args...)::type是一个参数类型需要在模板实例化了之后再去获取确定的类型。而class Fn是一个函数指针的模板变量。
第一个参数是std::launch上文已经提到过两个不同选项的区别了第二个参数是函数直接丢函数名就可以了第三个参数是这个函数的参数也是直接丢参数就可以了
如下是一个简单的调用示例并非完整示例
#include future // 头文件
// 函数
int add(int a, int b) {return a b;
}
// 调用
std::async(std::launch::async, add, 3, 5);调用了这个函数后CPP会帮我们创建一个线程来执行函数并根据第一个启动参数的不同决定啥时候创建这个线程。最终我们可以通过future获取到线程执行函数的返回值。
2.4 future
人如其名这个类型是用来声明一个未来的变量的。因为std::async会帮我们创建一个线程来执行函数此时该线程函数的返回值是未知的这个未来变量就是提前的一个声明当线程执行完毕函数并返回值的时候这个变量的值才真正被初始化为我们真正需要的那个值。
template class T future;
// specialization : T is a reference type (R)
template class R futureR;
// specialization : T is void
template futurevoid; 其有如下几个成员函数
get获取对应async所执行函数的返回值如果函数没有执行完毕则阻塞等待validbool判断当前future类型到底有没有和一个async函数所对应share将future对象转成一个std::shared_future对象wait等待异步任务完成但不获取结果wait_for等待异步任务完成但有等待的时长没等到就返回错误wait_until等待异步任务完成直到一个确定的时间没等到就返回错误
后面三个wait函数和CPP线程中的wait函数如出一辙。
2.5 share_future
share_future就好比share_ptr智能指针其让future对象从单一所有权变成了多人可用。本来是一个只能坐一人的餐桌现在变成了可以坐很多人的大桌子。
template class T shared_future;
template class R shared_futureR; // specialization : T is a reference type (R)
template shared_futurevoid; // specialization : T is void成员函数和future完全一样只不过么有share()函数这里就不赘述了
future是单人餐桌一次只能有一个线程执行get函数当get被执行后这个future会失效。share_future是大桌子所有人一起坐在这个桌子上等服务员上菜互不干扰
2.5 测试
2.5.1 正常测试
如下代码是一个简单的使用示例并且通过提供不同的std::launch启动策略我们也能观察到不同的现象
#include iostream
#include future
#include type_traits // result_of
#include sys/unistd.h //sleepint add(int a, int b)
{std::cout Add Thread std::this_thread::get_id() | Sleeping before add... std::endl;sleep(4);return a b;
}int main()
{std::cout Main Thread std::this_thread::get_id() | Start std::endl;//std::futureint futureResult std::async(std::launch::deferred, add, 3, 5); // 不会创建新线程std::futureint futureResult std::async(std::launch::async, add, 3, 5); // 创建新线程sleep(3); std::cout Main Thread std::this_thread::get_id() | Waiting for result... std::endl;int result futureResult.get(); // 等待异步任务完成并获取结果std::cout Main Thread std::this_thread::get_id() | Result: result std::endl;sleep(3);return 0;
}如果使用std::launch::async作为启动策略可以看到执行add函数的线程id和主线程的id是不同的通过linux下的ps -aL命令也能观察到出现两个线程
while :; do ps jax | head -1 ps -aL | grep -v grep;sleep 1; echo ########################; done程序执行输出结果
Main Thread 139869475694400 | Start
Add Thread 139869457655552 | Sleeping before add...
Main Thread 139869475694400 | Waiting for result...
Main Thread 139869475694400 | Result: 8但如果使用std::launch::deferred作为启动策略则会发现这两个线程的id是完全相同的这代表实际上其执行了并行的策略
Main Thread 139789724776256 | Start
Main Thread 139789724776256 | Waiting for result...
Add Thread 139789724776256 | Sleeping before add...
Main Thread 139789724776256 | Result: 82.5.2 多线程get一个future
在如下代码中我写了一个void future_get_func(std::futureint fu)的函数尝试开一个线程来get然后主执行流又get一次看看会发生什么。
#include iostream
#include future
#include functional
#include thread
#include type_traits // result_of
#include sys/unistd.h // sleepint add(int a, int b)
{std::cout Add Thread std::this_thread::get_id() | Sleeping before add... std::endl;sleep(4);return a b;
}void future_get_func_shared(std::shared_futureint fu)
{int result fu.get(); // 等待异步任务完成并获取结果std::cout Func Thread std::this_thread::get_id() | Result: result std::endl;sleep(2);
}void future_get_func(std::futureint fu)
{int result fu.get(); // 等待异步任务完成并获取结果std::cout Func Thread std::this_thread::get_id() | Result: result std::endl;sleep(2);
}int main()
{std::cout Main Thread std::this_thread::get_id() | Start std::endl;//std::futureint futureResult std::async(std::launch::deferred, add, 3, 5); // 不会创建新线程std::futureint futureResult std::async(std::launch::async, add, 3, 5); // 创建新线程sleep(3); std::cout Main Thread std::this_thread::get_id() | Waiting for result... std::endl;// 尝试测试多线程get会发生什么std::thread t1(future_get_func, std::ref(futureResult)); // 开个线程来gett1.detach(); // 直接分离线程sleep(1);int result futureResult.get(); // 等待异步任务完成并获取结果std::cout Main Thread std::this_thread::get_id() | Result: result std::endl;sleep(3);return 0;
}需要注意的是如下创建线程的传参必须要用std::ref包裹来告知线程这是一个引用对象否则编译会报错。因为 std::thread 要求参数可以在构造函数中被调用而 std::future 并不能直接传递给 std::thread
std::thread t1(future_get_func, std::ref(futureResult)); //正确
std::thread t1(future_get_func, futureResult); //错误编译通过后执行会发现跑出来了一个std::future_error异常代表我们在一个无效的future上调用了get函数。
Main Thread 139869577889600 | Start
Add Thread 139869559850752 | Sleeping before add...
Main Thread 139869577889600 | Waiting for result...
Func Thread 139869551458048 | Result: 8
terminate called after throwing an instance of std::future_errorwhat(): std::future_error: No associated state
Aborted记住了std::future在调用了一次get之后将不再与对应的std::async关联所以才会需要share_future的出现
改成share_future再执行上面这套逻辑就会发现成功跑起来了
Main Thread 140369883957056 | Start
Add Thread 140369865918208 | Sleeping before add...
Main Thread 140369883957056 | Waiting for result...
Func Thread 140369857525504 | Result: 8
Main Thread 140369883957056 | Result: 82.5.3 异常处理
int add(int a, int b)
{std::cout Add Thread std::this_thread::get_id() | Sleeping before add... std::endl;sleep(4);throw std::runtime_error(An error occurred);return a b;
}如果async执行的函数中抛出了异常那么这个异常将会被传回主执行流可以在主执行流中被处理。而如果直接使用线程来执行这个函数其异常不会被捕捉而是会导致整个进程退出。
下图使用async运行成功打印出异常捕获成功 下图使用线程运行进程退出 2.6 launch::deferred的真正意义
如果std::launch::deferred是同步执行这样写不是多此一举吗
NONONO 非也非也和直接调用Add函数相比用这样的方式调用Add函数还是有些区别的
推迟执行直接调用Add函数是立马执行但是用async可以推迟到调用get的时候才执行延迟计算有的时候我们并不是需要立马使用这个函数的返回值所以就可以延迟一会再执行这个函数先把函数的调用搞起来后面只需要一个get就能获取到结果了避免线程创建并不是什么时候多线程都更好有些时候创建一个线程的消耗还不如直接执行函数来的快比如函数干的活很小的情况
所以这个关键字多少还是有点作用了。
2.7 future_error/errc/status
除了future和share_future还有如下几个类型
std::future_error std::future_error 是一个异常类用于表示与 std::future 相关的错误。当在使用 std::future 时出现错误例如获取结果时异步任务抛出了异常就会抛出 std::future_error 异常。它是一个标准异常类型通常通过捕获异常对象来处理异步任务执行过程中的问题。std::future_errc std::future_errc 是一个枚举类型用于表示 std::future_error 中的不同错误情况。这样的枚举类型是为了在处理异常时更加明确和方便。它包含了一系列可能的错误如 broken_promisepromise 被破坏即 promise 对象的 set_value 或 set_exception 被多次调用和 future_already_retrievedfuture 对象已经被获取过一次等。std::future_status std::future_status 是一个枚举类型用于表示 std::future 的状态。它描述了一个 std::future 对象的当前情况指示异步任务是否已完成、是否有效等。std::future_status 包含三个值ready异步任务已完成可以获取结果、timeout等待超时即异步任务还未完成、deferred异步任务延迟执行。
3.为什么C会出现futrue
为啥要出一个future直接用老办法不也可以这么玩吗
C 标准库引入 std::future 和相关的异步编程机制是为了更好地支持并发编程和多线程环境。这些机制的出现有几个原因和动机
并发性和性能提升 在现代计算机体系结构中多核处理器已经成为常态。为了充分利用这些多核资源编写并发代码变得重要。std::future 提供了一种方式可以在多个线程中同时执行任务并且可以方便地获取任务的结果从而允许程序在多核处理器上并行执行提高性能。任务分离 在很多情况下我们希望将一个大的任务拆分成多个子任务在不同的线程中并行执行然后合并子任务的结果。std::future 允许你在一个线程中等待另一个线程的任务完成从而支持这种任务分离和并行执行的模式。避免阻塞 在传统的同步编程中如果某个操作需要等待会导致线程阻塞。而异步编程机制允许线程继续执行其他操作而不必等待一个潜在的耗时操作完成。std::future 允许你在一个线程中发起异步操作并在需要的时候获取操作的结果从而避免了不必要的阻塞。异常处理 在多线程环境中处理异步任务的异常可能变得复杂因为异步任务在不同的线程中执行。std::future 引入了异常传递机制允许异步任务在执行过程中抛出异常并将这些异常传递到等待结果的线程中。
总之C 的 std::future 和相关的异步编程机制提供了一种更高级别、更方便的方式来处理多线程并发编程。这些机制使得开发者能够更容易地利用多核处理器的性能并更灵活地设计并发代码从而在面对并发和异步任务时能够更好地管理资源、提高效率和处理异常。
4.promise
4.1 概念
std::promise 是 C 标准库中用于在一个线程中产生结果然后在另一个线程中获取结果的工具。它提供了一些成员函数来设置结果、处理异常以及获取关联的 std::future 对象等。
template class T promise;
template class R promiseR; // specialization : T is a reference type (R)
template promisevoid; // specialization : T is void下面是一些常用的 std::promise 成员函数及其用法 set_value 用于设置结果值。如果你已经通过 get_future() 获取了一个 std::future 对象调用 set_value 将会使等待结果的线程被唤醒并获取结果。 std::promiseint promiseObj;
std::futureint futureResult promiseObj.get_future();// 在某个线程中设置结果值
promiseObj.set_value(42);set_exception 用于设置异常将在等待结果的线程中抛出。这允许你在产生结果的线程中处理异常情况。 try {// 产生异常throw std::runtime_error(An error occurred);
}
catch (...)
{// 将异常设置到 promise 对象中promiseObj.set_exception(std::current_exception());
}get_future 返回与 std::promise 关联的 std::future 对象。通过这个 std::future你可以在另一个线程中等待并获取结果。 std::promiseint promiseObj;
std::futureint futureResult promiseObj.get_future();swap 交换两个 std::promise 对象的状态包括关联的 std::future 对象和设置的结果。 std::promiseint promise1;
std::promiseint promise2;// 交换两个 promise 对象的状态
promise1.swap(promise2);valid 检查 std::promise 对象是否有效即是否与一个 std::future 对象关联。 std::promiseint promiseObj;if (promiseObj.valid()) {// promiseObj 有效
} else {// promiseObj 无效
}还有下面这俩个成员函数看名字也能猜出来它是干嘛的就不多说了 set_value_at_thread_exit Set value at thread exit (public member function ) set_exception_at_thread_exit Set exception at thread exit (public member function )
这些成员函数允许你在一个线程中产生结果或异常并在另一个线程中等待和处理这些结果或异常。它们为多线程编程提供了一种可靠的方式来传递数据和控制流。请注意在使用 std::promise 时你需要仔细处理异常和线程同步以确保正确的结果传递。
4.2 示例
然后下头是一个基本的使用示例你可以理解为promise就是一个用来承担线程所执行函数的参数和异常的一个变量我们可以通过set_value并在主执行流中使用future.get来获取到这个值也可以设置异常并在主执行流中处理这个异常
不过promise和future一样是一次性的设置value和异常都只能设置一次设置完毕后就不能再设置了
#include iostream
#include futurevoid worker(std::promiseint p)
{p.set_value(42); // 设置值
}int main()
{std::promiseint promiseObj;std::futureint futureResult promiseObj.get_future();std::thread t(worker, std::ref(promiseObj)); // 通过线程执行t.join();int result futureResult.get(); // 主线程中获取值std::cout Result: result std::endl;return 0;
}5.packaged_task
std::packaged_task 是 C 标准库中的一个类模板用于将一个可调用对象函数、函数对象或可调用成员函数封装成一个可以异步执行的任务并且可以通过 std::future 获取任务的返回值。它在多线程编程中起到了连接异步任务和线程间通信的桥梁作用。
std::packaged_task 的主要作用有以下几个方面
封装任务 std::packaged_task 允许你将一个可调用对象封装成一个任务这个任务可以在另一个线程中异步执行。你可以将函数、函数对象或可调用成员函数封装为一个 std::packaged_task 实例。异步执行 通过将 std::packaged_task 实例传递给一个 std::thread 或其他支持异步执行的机制你可以在新的线程中执行封装的任务而不需要显式创建线程函数。获取返回值 std::packaged_task 可以与 std::future 一起使用以获取异步任务的返回值。你可以通过 packaged_task 的 get_future 方法获取一个与任务关联的 std::future 对象然后在适当的时候使用 std::future 的 get 方法来获取任务的返回值。线程池 std::packaged_task 结合线程池的使用可以更灵活地控制任务的执行方式。线程池可以预先创建一组线程然后将封装好的任务分配给这些线程执行避免了频繁创建和销毁线程的开销。
5.1 示例
下面是一个简单示例演示了如何使用 std::packaged_task 来异步执行一个函数并获取其返回值
#include iostream
#include thread
#include futureint add(int a, int b)
{return a b;
}int main()
{std::packaged_taskint(int, int) task(add);std::futureint future task.get_future();std::thread worker(std::move(task), 3, 5);worker.join(); // 通过线程异步执行这个taskint result future.get(); // 等待返回值std::cout Result: result std::endl;return 0;
}在这个例子中std::packaged_task 封装了一个函数 add然后通过 std::thread 异步执行最后通过 std::future 获取异步任务的返回值。
5.2 make_ready_at_thread_exit
packaged_task的成员函数中主要还是这个需要单独说明先看如下代码
#include iostream
#include thread
#include futureint main()
{std::packaged_taskint() task([](){ return 42; });std::futureint future task.get_future();std::thread worker([task]() {// 模拟一些工作std::this_thread::sleep_for(std::chrono::seconds(2));// 设置任务结果值task.make_ready_at_thread_exit();});worker.detach(); // 分离线程不等待其结束// 等待任务结果int result future.get();std::cout Result: result std::endl;return 0;
}我们让packaged_task执行的是一个return 42的函数而线程里面还会进行其他处理。
而make_ready_at_thread_exit()的作用就是确认当前的worker线程已经干完自己的活了可以执行packaged_task封装的函数了
相当于是一个确认packaged_task中封装的任务到底在什么时候执行的一个函数。调用这个函数的时候就会开始执行其包装的异步函数并返回结果给future.get()的执行流
The end
收工