目錄
- promise介紹
-
- 指派
- 取值
- packaged_task介紹
-
- valid
- 指派
- 取值
-
- 調用函數(取值準備)
-
- 方式一
- 方式二
- 狀态延遲
- 重置狀态
promise介紹
存在于頭檔案 future 中,相當于一個在并行環境中的資料存儲箱,各線程可以通過這一箱子傳遞某種類型的資料
舉個例子
如果我們要定義一個裝int類型的箱子
std::promise<int> boxOfInt
指派
- 如果我們要往箱子裡放東西,比如放一個10
箱子同時隻能放一個東西,如果還想多放(多次指派),就會報錯,terminate called after throwing an instance of ‘std::future_error’ what(): std::future_error: Promise already satisfied terminate called recursively
放好東西後,箱子就會給自己貼一個标簽(共享狀态标志變為ready)
這個标簽,也可以放的線程結束後再貼
如果我們把東西放進箱子後,
-
并不想馬上就把箱子關上(即馬上把共享标志變為ready),可以使用
set_value_at_thread_exit(),正如其字面意思,線上程結束後設值
void f(promise<int> &p)
{
p.set_value_at_thread_exit(123);//可以正常使用
}
void main()
{
promise<int> p;
auto re = p.get_future();
thread t(f, std::ref(p));
t.join();
cout << re.get() << endl;
system("pause");
}
注意:如果要使用set_value_at_thread_exit函數,要保證線上程退出時,promise對象不能被銷毀
什麼場景下會用 std::promise::set_value_at_thread_exit?
想象一下,如果有一個人一直在等另一個人往箱子裡放東西,等箱子裡有東西之後,它拿到東西就準備過河,但過河的橋還沒修好,東西卻已經準備好了。如果直接把東西放進箱子,它拿到東西,但橋都沒有,跑過去隻能掉河裡了。
set_value_at_thread_exit就是為了解決這個沖突,在資源沒有準備好的情況下,不告訴别人已經放好東西了(用來阻塞另外一個線程,實作對線程執行順序和共享資源的控制)
- 當然除了放某種類型的值之外,也可以在裡面放異常對象
boxOfException.set_exception(std::current_exception());
同理,也有set_exception_at_thread_exit和set_value_at_thread_exit類似
舉例說明
#include <iostream> // std::cin, std::cout, std::ios
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
#include <exception> // std::exception, std::current_exception
// 函數的目的是 擷取使用者輸入的資料
void get_int(std::promise<int>& prom) {
int x;
std::cout << "Please, enter an integer value: ";
std::cin.exceptions (std::ios::failbit); // throw on failbit
try {
std::cin >> x; // sets failbit if input is not int
prom.set_value(x);
} catch (std::exception&) {
// 當輸入的資料不是int類型的,無法裝入箱子時,就在裡面裝一個異常
prom.set_exception(std::current_exception());
}
}
void print_int(std::future<int>& fut) {
try {
int x = fut.get();
std::cout << "value: " << x << '\n';
} catch (std::exception& e) {
// 發現箱子裡裝進了一個異常,而不是一個資料,輸出
// [exception caught: basic_ios::clear: iostream error]
std::cout << "[exception caught: " << e.what() << "]\n";
}
}
int main ()
{
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread th1(get_int, std::ref(prom));
std::thread th2(print_int, std::ref(fut));
th1.join();
th2.join();
return 0;
}
-
更多神奇的指派操作
傳函數指針、可變元函數
https://blog.csdn.net/godmaycry/article/details/72844159
取值
要拿出箱子裡的東西
// future之後會講解
std::future<int> fut = boxOfInt.get_future();
int something = fut.get();
注意了,promise用完一次後,就無法繼續使用(get 隻能調用一次),如果繼續使用會出現,terminate called after throwing an instance of ‘std::future_error’ what(): std::future_error: Future already retrieved
可以想成成程式每次打開promise都是暴力拆箱,拆完箱子就爛了,隻能重新給它個新的。
( 箱子是獨一無二的,std::promise的普通指派操作是被禁用的,禁止拷貝)
boxOfInt = std::promise<int>(); // 指派為一個新的promise對象.
- 如果箱子裡沒有放東西,那就會拿不出來,程式就會阻塞,等待箱子裡有東西可以拿。
- 同樣箱子裡的東西,如果已經被拿走了,下一個來拿的程式,自然就會什麼都拿不到。
下面是一串可以正常運作的代碼
#include <iostream> // std::cout
#include <thread> // std::thread
#include <future> // std::promise, std::future
// 建立一個裝int類型的箱子
std::promise<int> boxOfInt;
// 這個函數目的就是從箱子裡取出東西
void print_global_promise () {
std::future<int> fut = boxOfInt.get_future();
int x = fut.get();
std::cout << "value: " << x << '\n';
}
int main ()
{
std::thread th1(print_global_promise);
// 往箱子裡放資料,th1線程就會去拿資料
boxOfInt.set_value(10);
th1.join();
// 箱子被th1弄爛了,隻好重新弄一個
boxOfInt = std::promise<int>();
// 同理 th2 也來開箱子
std::thread th2 (print_global_promise);
boxOfInt.set_value (20);
th2.join();
return 0;
}
可以自己嘗試注釋掉,放東西(set_value),做新箱子等步驟,檢視報錯的資訊。
packaged_task介紹
packaged_task 和 promise 比較類似,隻是promise是可以裝多種類型的箱子。
而packaged_task 更像是一種promise的一種特化,它是一個專門裝函數的箱子,使得函數的調用更加便捷
如果一個promise要裝函數
// 定義一個函數
int Test_Fun(int a, int b, int &c)
{
//a = 1, b = 2
c = a + b + 230;
return c;
}
// 聲明一個可調對象F
// 等同于typedef std::function<int(int, int, int&)> F;
using F = std::function<int(int, int, int&)>;
std::promise<F> pr1;
// 傳入函數Test_Fun
pr1.set_value(std::bind(&Test_Fun, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
// 取出函數
std::future<F> f = pr1.get_future();
auto fun = f.get(); //fun等同于Test_Fun,隻是被包裝成了lambda函數
// 如果要執行這個函數
int val = 3;
auto result = fun(1,2,val);
可以看出在整個的 裝入-取出-執行 的過程還是比較繁瑣的
valid
作用是檢查package是否和一個有效的共享狀态相關聯,
可以了解為檢視箱子裡面是否有東西存在,該成員函數在promise中是不存在的
使用預設構造函數生成的package_task對象,使用valid傳回的是一個false
隻有使用move指派,或者swap操作後才會變為true
// packaged_task::get_future
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread
// function that launches an int(int) packaged_task in a new thread:
std::future<int> launcher (std::packaged_task<int(int)>& tsk, int arg) {
if (tsk.valid()) {
std::future<int> ret = tsk.get_future();
std::thread (std::move(tsk),arg).detach();
return ret;
}
else return std::future<int>();
}
int main ()
{
std::packaged_task<int(int)> tsk ([](int x){return x*2;});
std::future<int> fut = launcher (tsk,25);
std::cout << "The double of 25 is " << fut.get() << ".\n";
return 0;
}
指派
而如果要在packaged_task中放函數
// 定義一個函數
int countdown (int from, int to) {
for (int i=from; i!=to; --i) {
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Finished!\n";
return from - to;
}
// 向packaged_task中放入函數
std::packaged_task<int(int,int)> task(countdown); // 設定 packaged_task
// 也可以使用 lambda 表達式初始化一個 packaged_task 對象.
std::packaged_task<int(int)> bar([](int x){return x*2;});
// 使用move-指派
std::packaged_task<int(int)> foo; // 預設構造函數.
foo = std::move(bar); // move-指派操作,也是 C++11 中的新特性.
package_task 和 promise 都是隻能使用 move指派 的,禁止使用 指派操作 運算
取值
調用函數(取值準備)
package_task的結果和promise一樣,也是通過future傳回的,
由于package_task裡面裝的是一個可調用對象,
如果不調用它,是不會有結果傳回來的。
std::package_task 不會自己啟動,必須我們自己去調用它。
方式一
// 調用方式1,使用thead啟動
#include <iostream>
#include <future>
#include <chrono>
#include <functional>
int Test_Fun(int a, int b, int &c)
{
//a=1,b=2,c=0
//突出效果,休眠5s
std::this_thread::sleep_for(std::chrono::seconds(5));
//c=233
c = a + b + 230;
return c;
}
int main()
{
//聲明一個std::packaged_task對象pt1,包裝函數Test_Fun
std::packaged_task<int(int, int, int&)> pt1(Test_Fun);
//聲明一個std::future對象fu1,包裝Test_Fun的傳回結果類型,并與pt1關聯
// 注意這裡的future裡面隻有一個int,因為future隻包含傳回值
std::future<int> fu1 = pt1.get_future();
//聲明一個變量
int c = 0;
//建立一個線程t1,将pt1及對應的參數放到線程裡面執行
// 通過 thread 調用函數
std::thread t1(std::move(pt1), 1, 2, std::ref(c));
//阻塞至線程t1結束(函數Test_Fun傳回結果)
int iResult = fu1.get();
std::cout << "執行結果:" << iResult << std::endl; //執行結果:233
std::cout << "c:" << c << std::endl; //c:233
return 1;
}
方式二
// 調用方式二,使用仿函數啟動
#include <iostream> // std::cout
#include <thread> // std::thread
#include <chrono>
#include <future>
using namespace std;
//普通函數
int Add(int x, int y)
{
return x + y;
}
void task_lambda()
{
//包裝可調用目标時lambda
packaged_task<int(int,int)> task([](int a, int b){ return a + b;});
//仿函數形式,啟動任務
task(2, 10);
//擷取共享狀态中的值,直到ready才能傳回結果或者異常
future<int> result = task.get_future();
cout << "task_lambda :" << result.get() << "\n";
}
void task_thread()
{
//包裝普通函數
std::packaged_task<int (int,int)> task(Add);
future<int> result = task.get_future();
//啟動任務,非異步
task(4,8);
cout << "task_thread :" << result.get() << "\n";
//重置共享狀态
task.reset();
result = task.get_future();
//通過線程啟動任務,異步啟動
thread td(move(task), 2, 10);
td.join();
//擷取執行結果
cout << "task_thread :" << result.get() << "\n";
}
int main(int argc, char *argv[])
{
task_lambda();
task_thread();
return 0;
}
//函數
int Test_Fun(int iVal)
{
std::cout << "Value is:" << iVal << std::endl;
return iVal + 232;
}
//聲明一個std::packaged_task對象pt1,包裝函數Test_Fun
std::packaged_task<int(int)> pt1(Test_Fun);
//聲明一個std::future對象,包裝Test_Fun的傳回結果,并與pt1關聯
std::future<int> fu1 = pt1.get_future();
// 這裡添加一種啟動方式
// pt1(1);
// std::thread t1(std::move(pt1),1); t1.join();
int result = fu1.get()
注意:使用std::packaged_task關聯的std::future對象儲存的資料類型是可調對象的傳回結果類型,
如示例函數的傳回結果類型是int,那麼聲明為std::future< int >,而不是std::future< int (int) >
狀态延遲
在promise中,如果想線上程結束後,才把共享狀态置為ready,
使用的是set_value_at_thread_exit/set_exception_at_thread_exit
在packaged_task中使用的是make_ready_at_thread_exit
// Not supported by g++ 4.9.3 or Visual Studio 2013
#include <future>
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <utility>
void worker(std::future<void>& output)
{
std::packaged_task<void(bool&)> my_task{ [](bool& done) { done=true; } };
auto result = my_task.get_future();
bool done = false;
my_task.make_ready_at_thread_exit(done); // execute task right away
std::cout << "worker: done = " << std::boolalpha << done << std::endl;
auto status = result.wait_for(std::chrono::seconds(0));
if (status == std::future_status::timeout)
std::cout << "worker: result is not ready yet" << std::endl;
output = std::move(result);
}
int main()
{
std::future<void> result;
std::thread{worker, std::ref(result)}.join();
auto status = result.wait_for(std::chrono::seconds(0));
if (status == std::future_status::ready)
std::cout << "main: result is ready" << std::endl;
}
重置狀态
promise 在使用完一次後(即成功取值後),就無法繼續使用了
而packaged_task,在使用過一次後,可以通過reset函數重獲新生
#include <iostream>
#include <cmath>
#include <thread>
#include <future>
int main()
{
std::packaged_task<int(int,int)> task([](int a, int b) {
return std::pow(a, b);
});
std::future<int> result = task.get_future();
task(2, 9);
std::cout << "2^9 = " << result.get() << '\n';
task.reset();
result = task.get_future();
std::thread task_td(std::move(task), 2, 10);
task_td.join();
std::cout << "2^10 = " << result.get() << '\n';
}
/* Output:
2^9 = 512
2^10 = 1024
*/
參考
- https://www.cnblogs.com/haippy/p/3239248.html
- https://mp.weixin.qq.com/s?src=11×tamp=1601175837&ver=2609&signature=SGV0wfjTBnqACPlLkyatqBIP2TGLeFoC0QP0UnwMrbjTHkSFD*Rix2vGu4F-WZLkTTOWvF05LT6Hw3tNf9jFn1pMdSbCcs9DvyokI2V0bK1ILpRxIcu3X0CF28r9Vjdi&new=1
- https://blog.csdn.net/godmaycry/article/details/72844159
- https://blog.csdn.net/t114211200/article/details/78082703
- http://www.cplusplus.com/reference/future/promise/
- http://www.cplusplus.com/reference/future/packaged_task/
- https://blog.csdn.net/godmaycry/article/details/72868559
- https://blog.csdn.net/c_base_jin/article/details/79541301?utm_medium=distribute.pc_relevant.none-task-blog-title-1&spm=1001.2101.3001.4242
- https://blog.csdn.net/nirendao/article/details/52079942