【C++多线程】std::future、std::async、std::promise、std::packaged_task、std::shared_future
如图以下是头文件<future>中的类容。
std::future<T>
future有两个类模板,一个独占的std::future,也就是只能被获取一次,另一个是共享的std::shared_future。std::future<T>是一个类模板,其中T是要存储的值的类型,std::future 的实例只能与一个指定事件相关联。std::future对象在内部存储一个将来会被赋值的值,并提供了一个访问该值的机制,通过get()成员函数实现。但如果有人视图在get()函数可用之前通过它来访问相关的值,那么get()函数将会阻塞,直到该值可用。std::future的一个对象,可以从某个对象(std::promise和std::packaged_task)或函数(std::async())获取值,并在不同线程之间提供恰当的同步访问。如std::async 会返回一个 std::future 对象,这个对象持有最终计算出来的结果。当你需要这个值时,你只需要调用这个对象的get()成员函数,他会阻塞到当前位置,知道获取到那个返回的值。如前所述,std::future通常与std::async()函数和std::promise、std::packaged_tast对象一起使用。
- std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()但是,它是可以获取结果的。
- std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。
- std::future对象的share()成员函数,将该future对象返回为shared_future的对象。
1 // future example 2 #include <iostream> // std::cout 3 #include <future> // std::async, std::future 4 #include <chrono> // std::chrono::milliseconds 5 6 // a non-optimized way of checking for prime numbers: 7 bool is_prime (int x) { 8 for (int i=2; i<x; ++i) if (x%i==0) return false; 9 return true; 10 } 11 12 int main () 13 { 14 // call function asynchronously: 15 std::future<bool> fut = std::async (is_prime,444444443); 16 17 // do something while waiting for function to set future: 18 std::cout << "checking, please wait"; 19 std::chrono::milliseconds span (100); 20 while (fut.wait_for(span)==std::future_status::timeout) 21 std::cout << '.' << std::flush; 22 23 bool x = fut.get(); // retrieve return value 24 25 std::cout << "\n444444443 " << (x?"is":"is not") << " prime.\n"; 26 27 return 0; 28 }
std::future_status是枚举类型,表示异步任务的执行状态。std::future和std::shared_future的成员函数wait_for()和wait_until()会返回该类型。类型的取值有
-
std::future_status::ready
-
std::future_status::timeout
-
std::future_status::deferred
std::async()
std::async()是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象。std::async()使用方法和std::thread()类似。
1 #include <iostream> 2 #include <future> 3 using namespace std; 4 class A { 5 public: 6 int mythread(int mypar) { 7 cout << mypar << endl; 8 return mypar; 9 } 10 }; 11 12 13 int mythread() { 14 cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl; 15 std::chrono::milliseconds dura(5000); 16 std::this_thread::sleep_for(dura); 17 cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl; 18 return 5; 19 } 20 21 22 int main() { 23 A a; 24 int tmp = 12; 25 cout << "main" << "threadid = " << std::this_thread::get_id() << endl; 26 std::future<int> result1 = std::async(mythread); 27 cout << "continue........" << endl; 28 cout << result1.get() << endl; //阻塞在这里等待mythread()执行完毕,拿到结果 29 30 //类成员函数 31 std::future<int> result2 = std::async(&A::mythread, &a, tmp); //第二个参数是对象引用才能保证线程里执行的是同一个对象 32 cout << result2.get() << endl; 33 //或者result2.wait(); 34 cout << "good luck" << endl; 35 return 0; 36 }
我们通过向std::async()传递一个参数,改参数是std::launch类型(枚举类型),来达到一些特殊的目的:
1、std::lunch::deferred,(defer推迟,延期)表示线程入口函数的调用会被延迟,一直到std::future的wait()或者get()函数被调用时(由主线程调用)才会执行;如果wait()或者get()没有被调用,则不会执行。
实际上根本就没有创建新线程。std::lunch::deferred意思时延迟调用,并没有创建新线程,是在主线程中调用的线程入口函数。
2、std::launch::async,在调用async函数的时候就强制创建新线程。
3、std::launch::async | std::launch::deferred,这是std::async()的默认情况,意味着std::asyn()的行为由系统自行决定异步还是同步运行。可能是 std::launch::async 创建新线程立即执行, 也可能是 std::launch::deferred 没有创建新线程并且延迟到调用get()执行,由系统根据实际情况来决定采取哪种方案。
如果想要确定std::async()是否创建了线程,可以通过使用std::future对象的成员函数wait_for()返回的std::future_status状态来确定。
1 #include <iostream> 2 #include <future> 3 using namespace std; 4 5 int mythread() { 6 cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl; 7 std::chrono::milliseconds dura(5000); 8 std::this_thread::sleep_for(dura); 9 cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl; 10 return 5; 11 } 12 13 14 int main() { 15 cout << "main" << "threadid = " << std::this_thread::get_id() << endl; 16 std::future<int> result1 = std::async(std::launch::deferred ,mythread); 17 cout << "continue........" << endl; 18 cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果 19 cout << "good luck" << endl; 20 return 0; 21 }
std::async和std::thread()区别
std::thread()如果系统资源紧张可能出现创建线程失败的情况,如果创建线程失败那么程序就可能崩溃,而且不容易拿到函数返回值(不是拿不到)
std::async()创建异步任务。可能创建线程也可能不创建线程,并且容易拿到线程入口函数的返回值,因为他会返回一个std::future对象;
std::thread产生的线程需要在主线程中调用需要join或者detach,否则会出现异常,而std::async产生的线程不需要我们做任何处理。
由于系统资源限制:
①如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,崩溃。
②如果用std::async,一般就不会报异常,因为如果系统资源紧张,无法创建新线程的时候,async不加额外参数的调用方式就不会创建新线程。而是在后续调用get()请求结果时执行在这个调用get()的线程上。
std::promise<T>
还有让std::future 与一个任务实例相关联的唯一方式,可以将任务包装入一个 std::packaged_task<> 实例中,或使用 std::promise<> 类型模板显示设置值。与 std::promise<> 对比, std::packaged_task<> 具有更高层的抽象。
std::promise也是一个类模板,其对象有可能在将来对值进行赋值,每个std::promise对象有一个对应的std::future对象, std::promise保存的值可被与之关联的std::future读取,读取操作可以发生在其它线程。std::promise允许move语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值),std::future亦然。std::promise<void>
是合法的,此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞。
std::promise和std::future合作共同实现了多线程间通信。
1 #include <iostream> 2 #include <thread> 3 #include <future> 4 #include <chrono> 5 6 // 线程B 7 void initiazer(std::promise<int> * promObj) 8 { 9 std::cout << "Thread B" << std::endl; 10 // set the value at proper time 11 std::this_thread::sleep_for(std::chrono::seconds(3)); 12 promObj->set_value(23); 13 } 14 15 int main() 16 { 17 // 线程A 18 std::promise<int> promiseObj; 19 std::future<int> futureObj = promiseObj.get_future(); 20 21 std::thread th(initiazer, &promiseObj); // 启动线程B 22 23 // 获取对象的值,该调用在B设置其值后会返回23,在B设置其值前会阻塞 24 std::cout<< futureObj.get() << std::endl; 25 26 th.join(); 27 28 return 0; 29 } 30 31 //输出23
1 #include <iostream> // std::cout, std::endl 2 #include <thread> // std::thread 3 #include <string> // std::string 4 #include <future> // std::promise, std::future 5 #include <chrono> // seconds 6 using namespace std::chrono; 7 //线程B 8 void read(std::future<std::string> *future) { 9 // future会一直阻塞,直到有值到来 10 std::cout << future->get() << std::endl; 11 } 12 //线程A 13 int main() { 14 // promise 相当于生产者 15 std::promise<std::string> promise; 16 // future 相当于消费者, 右值构造 17 std::future<std::string> future = promise.get_future(); 18 // 另一线程中通过future来读取promise的值 19 std::thread thread(read, &future); 20 // 让read等一会儿:) 21 std::this_thread::sleep_for(seconds(1)); 22 // 23 promise.set_value("hello future"); 24 // 等待线程执行完成 25 thread.join(); 26 27 return 0; 28 } 29 // 控制台输: hello future
如上代码中,一旦std::promise对象调用set_value设置了对象的值,该对象的共享状态就变更为ready,std::future对象就能使用get()函数获取到值。
注意:
- 只能从promise共享状态获取一个future对象,不能把两个future关联到同一个promise
- 如果promise不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error 异常(broken_promise)来设置其自身的就绪状态
- promise 对象的set_value只能被调用一次,多次调用会抛出std::future_error异常(因为第一次调用后状态变更为ready)
- std::future是通过std::promise::get_future获取到的,自己构造出来的无效
1 #include <iostream> // std::cout, std::endl 2 #include <thread> // std::thread 3 #include <future> // std::promise, std::future 4 #include <chrono> // seconds 5 using namespace std::chrono; 6 7 void read(std::future<int> future) { 8 try { 9 future.get(); 10 } catch(std::future_error &e) { 11 std::cerr << e.code() << "\n" << e.what() << std::endl; 12 } 13 } 14 15 int main() { 16 std::thread thread; 17 { 18 // 如果promise不设置任何值 19 // 则在promise析构时会自动设置为future_error 20 // 这会造成future.get抛出该异常 21 std::promise<int> promise; 22 thread = std::thread(read, promise.get_future()); 23 } 24 thread.join(); 25 26 return 0; 27 }
通过std::promise::set_exception函数可以设置自定义异常,该异常最终会被传递到std::future,并在其get函数中被抛出。
1 #include <iostream> 2 #include <future> 3 #include <thread> 4 #include <exception> // std::make_exception_ptr 5 #include <stdexcept> // std::logic_error 6 7 void catch_error(std::future<void> &future) { 8 try { 9 future.get(); 10 } catch (std::logic_error &e) { 11 std::cerr << "logic_error: " << e.what() << std::endl; 12 } 13 } 14 15 int main() { 16 std::promise<void> promise; 17 std::future<void> future = promise.get_future(); 18 19 std::thread thread(catch_error, std::ref(future)); 20 // 自定义异常需要使用make_exception_ptr转换一下 21 promise.set_exception( 22 std::make_exception_ptr(std::logic_error("caught"))); 23 24 thread.join(); 25 return 0; 26 } 27 // 输出:logic_error: caught
std::packaged_task<T>
std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。
std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。
可以通过 std::packged_task::get_future 来获取与共享状态相关联的 std::future 对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:
- std::packaged_task 对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。
- std::future 对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为 ready.
std::packaged_task 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。
1 #include <iostream> // std::cout 2 #include <future> // std::packaged_task, std::future 3 #include <chrono> // std::chrono::seconds 4 #include <thread> // std::thread, std::this_thread::sleep_for 5 6 // count down taking a second for each value: 7 int countdown (int from, int to) { 8 for (int i=from; i!=to; --i) { 9 std::cout << i << '\n'; 10 std::this_thread::sleep_for(std::chrono::seconds(1)); 11 } 12 std::cout << "Finished!\n"; 13 return from - to; 14 } 15 16 int main () 17 { 18 std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task 19 std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象. 20 21 std::thread th(std::move(task), 10, 0); //创建一个新线程完成计数任务. 22 23 int value = ret.get(); // 等待任务完成并获取结果. 24 25 std::cout << "The countdown lasted for " << value << " seconds.\n"; 26 27 th.join(); 28 return 0; 29 }
std::shared_future<T>
std::shared_future:也是个类模板,可以让多个线程等待同一个事件,用法和std::future差不多。区别是std::future的 get() 成员函数是转移数据,只能get()一次; std::shared_future 的 get()成员函数是复制数据,可以get()多次。
获取多次
1 #include <thread> 2 #include <iostream> 3 #include <future> 4 using namespace std; 5 6 int mythread() { 7 cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl; 8 std::chrono::milliseconds dura(5000); 9 std::this_thread::sleep_for(dura); 10 cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl; 11 return 5; 12 } 13 14 int main() { 15 cout << "main" << "threadid = " << std::this_thread::get_id() << endl; 16 std::packaged_task<int()> mypt(mythread); 17 std::thread t1(std::ref(mypt)); 18 std::future<int> result = mypt.get_future(); 19 20 bool ifcanget = result.valid(); //判断future 中的值是不是一个有效值 21 std::shared_future<int> result_s(result.share()); //执行完毕后result_s里有值,而result里空了 22 //std::shared_future<int> result_s(std::move(result)); 23 //通过get_future返回值直接构造一个shared_future对象 24 //std::shared_future<int> result_s(mypt.get_future()); 25 t1.join(); 26 27 auto myresult1 = result_s.get(); 28 auto myresult2 = result_s.get(); 29 30 cout << "good luck" << endl; 31 return 0; 32 }
在每一个 std::shared_future 的独立对象上成员函数调用返回的结果还是不同步的,所以为了在多个线程访问一个独立对象时,避免数据竞争,必须使用锁来对访问进行保护。优先使用的办法:为了替代只有一个拷贝对象的情况,可以让每个线程都拥有自己对应的拷贝对象。这样,当每个线程都通过自己拥有的 std::shared_future 对象获取结果,那么多个线程访问共享同步结果就是安全的。
参考
http://www.cplusplus.com/reference/future/
https://blog.csdn.net/qq_38231713/article/details/106092879
https://www.jianshu.com/p/7945428c220e