一,std::future与std::promise
std::future是一个类模板,存放了线程入口函数的返回结果,调用std::future对象的get()函数可以拿到返回结果。
std::promise也是一个类模板,可以基于std::promise实现线程之间的数据传输。
构造一个std::promise对象时,可以和std::future对象相互关联。
1.std::thread与std::future的对比
std::thread启动的线程不容易获取线程的计算结果。
std::thread启动的线程如果抛出了异常,且异常没有被线程本身处理的时候,这个线程会导致整个应用程序发生终止。
std::future可以很方便地获取线程的执行结果,如果线程抛出了异常,std::future可以将异常转移到另一个线程中,让另一个线程来处理异常。
代码样例: std::future调用get()传递异常给另一个线程
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>
//线程函数
int CalculateSum() {
throw std::runtime_error("Exception throw from CalculateSum.");
}
int main()
{
auto future_obj = std::async(std::launch::async, CalculateSum);
try {
int res = future_obj.get();
std::cout << res << std::endl;
}
catch (const std::exception& ex) {
std::cout << "Caught exception: " << ex.what() << std::endl;
}
}
运行结果:
代码语言:javascript复制Caught exception: Exception throw from CalculateSum.
2.std::promise和std::future的区别
同一个线程或者另一个线程将线程函数的计算结果放入到std::promise中,而std::future可以获取std::promise中存储的线程计算结果。因此,std::promise是线程计算结果的输入端,std::future是线程计算结果的输出端。
3.std::future的常用成员函数
1.get:阻塞式地获得线程返回结果。
2.wait:等待结果变得可用,此时不会获取线程的执行结果。
3.wait_for:非阻塞式地获得线程返回结果。
std::future通过get()获取线程执行结果,如果线程尚未执行结束,对get()的调用将阻塞,直到该结果可以被获取。
std::future可以先通过调用wait_for()方法,查询结果是否可用来避免阻塞。
std::future只能调用一次get()成员函数来获取结果,继续调用多次会引发异常。
4.std::promise的常用成员函数
1.set_value:指定线程返回结果。
2.get_future:返回与线程关联的future。
3.set_exception:指定线程返回的异常。
std::promise对象只能被移动,不能被复制。
代码样例:子线程和主线程之间同步字符串数据
代码语言:javascript复制#include <iostream>
#include <thread>
#include <future>
void modifyMessage(std::promise<std::string>&& proms, std::string msg)
{
std::string metaMsg = msg " has been modified";
proms.set_value(metaMsg);
}
int main()
{
std::string msg_str = "My Message";
//创建promise对象
std::promise<std::string> proms;
//创建一个关联的future对象
std::future<std::string> future_obj = proms.get_future();
//给线程传递promise对象
std::thread t(modifyMessage, std::move(proms), msg_str);
//打印原始msg_str
std::cout << "Original message from main(): " << msg_str << std::endl;
//打印被子线程修改的msg_str
std::string messageFromThread = future_obj.get();
std::cout << "Modified message from thread(): " << messageFromThread << std::endl;
t.join();
return 0;
}
运行结果:
代码语言:javascript复制Original message from main(): My Message
Modified message from thread(): My Message has been modified
二,std::shared_future使用说明
std::shared_future是一个类模板,用法和std::future相似。
std::shared_future可以让多个线程共享同一个状态,从而实现多线程通信。
std::shared_future的常用成员函数
1.get:阻塞式地获得线程返回结果。
2.wait:等待结果变得可用,此时不会获取线程的执行结果。
3.wait_for:非阻塞式地获得线程返回结果。
std::shared_future的成员函数的用法和std::future基本一致,主要区别在于,std::shared_future的get()函数是用来复制数据的,而不是移动数据,这样设计可以让多个线程都可以通过get()获取结果。因此,std::future对象只能执行一次get()函数,而std::shared_future对象可以执行多次get()函数。
三,std::async使用说明
std::async是一个函数模板,通常用来启动一个异步任务,std::async执行结束会返回一个std::future对象。
1.std::async的传参方式
std::async传参的方式和std::thread十分类似。
可以使用std::launch给std::async传参,std::launch可以控制是否给std::async创建新线程。
当不指定std::launch参数时,std::async根据系统资源,自行选择一种执行方法。
结合传参方式,可以总结出,std::async执行线程函数的方法有两种:
1.创建一个新的线程,异步执行线程函数。
2.不创建新线程,在主调线程上同步执行线程函数。
通过传参std::launch来让std::async选择指定方式执行线程函数的方法有三种:
std::launch::async:创建新线程,异步执行线程函数。
std::launch::deferred:返回的std::future对象显式调用get()时,在主调线程上同步执行线程函数。
std::launch::async | std::launch::deferred:代码运行时根据系统资源,选择默认的执行方法。
代码样例:
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
//线程函数
int CalculateSum(int a, int b) {
std::cout << "In other thread." << std::endl;
return a b;
}
int main()
{
auto future_obj = std::async(CalculateSum, 12, 16);
std::cout << "In Main thread." << std::endl;
int res = future_obj.get();
std::cout << res << std::endl;
}
运行结果:
代码语言:javascript复制In Main thread.In other thread.
28
2.std::async和std::thread的区别
std::thread直接创建线程,而std::async异步创建一个任务,这个任务可以创建新线程,也可以不创建线程,可以避免占用系统资源。
由于std::async不一定会创建新线程,因此,当系统内存资源不足的时候,继续运行std::thread会使系统崩溃,而std::async此时不会创建新线程,避免了系统崩溃。
std::thread创建的线程不容易获取线程函数的返回值,std::async执行完返回一个std::future对象,可以很容易获取线程函数的返回值。
四,std::packaged_task包装器
std::packaged_task包装器可以生成一个可调用的对象,并且允许异步获取该对象的执行结果。
std::packaged_task是一个类模板,常用的成员函数是get_future(),用于返回一个关联的std::future对象,使用std::packaged_task时可以不需要显式地使用std::promise。
代码样例:
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
//线程函数
int CalculateSum(int a, int b) {
return a b;
}
int main()
{
std::packaged_task<int(int, int)> task(CalculateSum);
auto future_obj = task.get_future();
std::thread thread_01{ std::move(task), 12, 16 };
int res = future_obj.get();
std::cout << res << std::endl;
thread_01.join();
}
运行结果:
代码语言:javascript复制28
使用std::packaged_task可以将各种可调用对象包装起来,方便作为线程的入口函数来调用。
代码样例:
Demo1:packaged_task包装普通函数,然后被调用
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>
using namespace std;
int mythread(int n)
{
cout << "Thread Input: " << n << endl;
cout << "mythread start " << "threadId=" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
cout << "mythread end " << "threadId=" << std::this_thread::get_id() << endl;
return 5;
}
int main()
{
cout << "Main thread start " << "threadId=" << std::this_thread::get_id() << endl;
std::packaged_task <int(int)> mypt(mythread);
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> res = mypt.get_future();
cout << "Thread Output: " << res.get() << endl;
cout << "Main thread end " << "threadId=" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
代码语言:javascript复制Main thread start threadId=4340
Thread Input: 1
mythread start threadId=12168
mythread end threadId=12168
Thread Output: 5
Main thread end threadId=4340
Demo2:packaged_task包装lambda表达式,然后被调用
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
cout << "Main thread start " << "threadId=" << std::this_thread::get_id() << endl;
std::packaged_task <int(int)> mypt([](int n){
cout << "Thread Input: " << n << endl;
cout << "mythread start " << "threadId=" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
cout << "mythread end " << "threadId=" << std::this_thread::get_id() << endl;
return 10;
});
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> res = mypt.get_future();
cout << "Thread Output: " << res.get() << endl;
cout << "Main thread end " << "threadId=" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
代码语言:javascript复制Main thread start threadId=19596
Thread Input: 1
mythread start threadId=21124
mythread end threadId=21124
Thread Output: 10
Main thread end threadId=19596
Demo3:packaged_task包装成对象,然后被调用
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <future>
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
cout << "Main thread start " << "threadId=" << std::this_thread::get_id() << endl;
std::packaged_task <int(int)> mypt([](int n){
cout << "Thread Input: " << n << endl;
cout << "mythread start " << "threadId=" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
cout << "mythread end " << "threadId=" << std::this_thread::get_id() << endl;
return 15;
});
mypt(10); //可调用对象
std::future<int> res = mypt.get_future();
cout << "Thread Output: " << res.get() << endl;
cout << "Main thread end " << "threadId=" << std::this_thread::get_id() << endl;
return 0;
}
运行结果:
代码语言:javascript复制Main thread start threadId=20868
Thread Input: 10
mythread start threadId=20868
mythread end threadId=20868
Thread Output: 15
Main thread end threadId=20868
五,参考阅读
《C 新经典》
《C 高级编程》
《深入理解C 11:C 11新特性解析与应用》