std::async 函数模板 与 std::future 类模板

std::async()****

std::async() 是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个 std::future 对象,std::future 是一个类模板

  • 什么叫 “启动一个异步任务”

    启动一个异步任务就是自动创建一个线程并开始执行对应的线程入口函数,它返回一个 std::future 对象

    这个对象里面就含有线程入口函数所返回的结果(线程返回的结果)我们可以通过调用 future 对象的成员函数 get() 来获取结果

  • 函数中可以额外的传递一个参数

    该参数类型是 std::lunnch 类型(枚举)来达到一些目的

    1. std::launch::deferred 表示线程入口函数调用被延迟到 std::futurewait()get() 函数时调用才执行,延迟调用,并且没有创建新线程,是在主线程中调用线程入口函数

      如果 wait()get() 没有被调用 则线程不会被执行

    2. std::launch::async 在调用 async() 函数的时候就开始创建线程,不会等待 get()

std::future

std::future 提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future 中保存着一个值,这个值是在将来的某个时刻能够拿到。

std::future 对象的 get() 成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像 join()。但是,它是可以获取结果的。

std::future 对象的 wait() 成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::threadjoin() 更像。

具体实例

类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
public:
// 线程入口函数
int mythread(int mypar)
{
cout << mypar << endl;
cout << "mythread() start threadID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dr(5000); // 定义一个5s的时间 模拟工作了这么久
std::this_thread::sleep_for(dr);
cout << "mythread() end threadID = " << std::this_thread::get_id() << endl;
return 5;
}
};

main函数

1
2
3
4
5
6
7
8
9
10
11
int main()
{
A a;
int tmpper = 12;
cout << "main() threadID = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(&A::mythread, &a, tmpper); // 第二个参数是对象引用才能保证线程里用的是同一个对象
// std::future<int> result = std::async(std::launch::deferred, &A::mythread, &a, tmpper);
// std::future<int> result = std::async(std::launch::async, &A::mythread, &a, tmpper);
cout << "return value = " << result.get() << endl;
cout << "main() End!" << endl;
}
  1. 使用 std::launch::async

    image-20211117190358167

image-20211117190444180

async()强制创建了一个新的线程

  1. 使用 std::launch::deferred

    image-20211117190712373

    则是在主线程中调用了这个函数,并没有创建新的线程

  2. 使用 std::launch::deferred | std::launch::async

    这里这个 | : 这意味着 std::async 的行为可能是 创建新线程并立即执行 或者 创建新线程并且延迟调用

    系统自动决定异步还是同步方式运行

  3. 不带额外参数

    与 3. 中效果完全一致

std::async和std::thread()区别

std::thread() 如果系统资源紧张可能出现创建线程失败的情况,如果创建线程失败那么程序就可能崩溃,而且不容易拿到函数返回值(不是拿不到)
std::async() 创建异步任务。可能创建线程也可能不创建线程,并且容易拿到线程入口函数的返回值;

由于系统资源限制:

  1. 如果用 std::thread 创建线程太多,则可能创建失败,系统报告异常崩溃
  2. 如果用 std::async 一般就不会报异常不会崩溃,因为,如果系统资源紧张导致无法创建新线程的时候,std::async 这种不加额外参数的调用就不会创建新线程。而是后续调用了.get() 来请求结果,那么这个异步任务就运行在执行这条 get() 语句所在的线程上

std::packaged_task 类模板

std::packaged_task: 打包任务,把任务包装起来,可用于包装各种可调用对象的模板类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 线程入口函数
int mythread(int mypar)
{
cout << mypar << endl;
cout << "mythread() start threadID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dr(5000); // 定义一个5s的时间 模拟工作了这么久
std::this_thread::sleep_for(dr);
cout << "mythread() end threadID = " << std::this_thread::get_id() << endl;
return 5;
}

int main()
{
std::packaged_task<int(int)>> mypt(mythread);
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
}

std::future 对象里包含有线程入口函数的返回结果,result 保存 mythread 的返回值.

我们也可以吧函数换成 lambda 表达式

1
2
3
4
5
6
7
8
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start threadID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dr(5000); // 定义一个5s的时间 模拟工作了这么久
std::this_thread::sleep_for(dr);
cout << "mythread() end threadID = " << std::this_thread::get_id() << endl;
return 5;
});

std::packaged_task也是可调用对象

lambda 表达式的直接调用

1
2
3
4
5
6
7
8
9
10
11
12
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start threadID = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dr(5000); // 定义一个5s的时间 模拟工作了这么久
std::this_thread::sleep_for(dr);
cout << "mythread() end threadID = " << std::this_thread::get_id() << endl;
return 5;
});

mypt(150); // 直接调用,并没有创建线程
std::future<int> result = mypt.get_future();
cout << result.get() << endl;

使用容器保存任务

1
2
3
4
5
6
7
8
9
std::vector<std::packaged_task<int(int)>> my_tasks;
my_tasks.emplace_back(std::move(mypt)); // 使用移动语义,加入进去后 mypt 就空了
// 从容器中取出 packaged_task
auto iter = my_tasks.begin();
std::packaged_task<int(int)> mypt2 = std::move(*iter); // move 后 容器中还有一项,并没有被删除
my_tasks.erase(iter); // 删除第一个元素,迭代器已经失效,所以后续代码不能再使用 iter
mypt2(150);
std::future<int> result = mypt2.get_future();
cout << result.get() << endl;

std::promise 类模板

通过 promise 保存一个值,在将来某个时刻我们通过把一个 future 绑定到 promise 上来得到这个绑定的值

可以通过 promise 来实现两个线程中的数据传递

1
2
3
4
5
6
7
std::promise<int> myprom;   // 声明一个 std::promise 对象,保存值为int
std::thread t1(mythread, std::ref(myprom), 180);
t1.join();

std::future<int> ful = myprom.get_future(); // promise 和 future 绑定,用于获取线程返回值
std::thread t2(mythread2, std::ref(ful)); // 两个线程中数据传递
t2.join();