C++并发编程(C++11)
Posted 菜鸟502
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++并发编程(C++11)相关的知识,希望对你有一定的参考价值。
前言
首先需要说明,本博客的主要内容参考自Forhappy && Haippy博主的分享,本人主要是参照博主的资料进行了学习和总结,并适当的衍生或补充了相关的其他知识内容。
C++11有了std::thread 以后,可以在语言层面编写多线程程序了,直接的好处就是多线程程序的可移植性得到了很大的提高。
C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是,,,和。
:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
1. HelloWorld
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <thread>
using namespace std;
void thread_task()
{
cout << "Hello World!" << std::endl;
}
int main(int argc, const char *argv[])
{
thread t(thread_task);
t.join();
system("pause");
return 0;
}
2. Thread Constructor
1)默认构造函数:thread() noexcept,创建一个空的 thread 执行对象。
2)初始化构造函数: template
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
using namespace std;
void exec_proc1(int n)
{
for (int i = 0; i < 5; ++i) {
cout << "pass value, executing thread " << n << endl;
//阻止线程运行到10毫秒
this_thread::sleep_for(chrono::milliseconds(10));
}
}
void exec_proc2(int& n)
{
for (int i = 0; i < 5; ++i) {
cout << "pass reference, executing thread " << n << endl;
++n;
//阻止线程运行到10毫秒
this_thread::sleep_for(chrono::milliseconds(10));
}
}
int main()
{
int n = 0;
// t1,使用默认构造函数,什么都没做
thread t1;
// t2,使用有参构造函数,传入函数名称(地址)exec_pro1,并以传值的方式传入args
// 将会执行exec_proc1中的代码
thread t2(exec_proc1, n + 1);
// t3,使用有参构造函数,传入函数名称(地址)exec_pro1,并以传引用的方式传入args
// 将会执行exec_proc1中的代码
thread t3(exec_proc2, ref(n));
// t4,使用移动构造函数,由t4接管t3的任务,t3不再是线程了
thread t4(move(t3));
// 可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.
t2.join();
t4.join();
cout << "the result of n is " << n << endl;
system("pause");
return 0;
}
3. 赋值操作
1)move 赋值操作:thread& operator= (thread&& rhs) noexcept,如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;如果当前对象可被 joinable,则 terminate() 报错。
2)拷贝赋值操作被禁用:thread& operator= (const thread&) = delete,thread 对象不可被拷贝。
#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <iostream>
#include <thread>
using namespace std;
void exec_produce(int duration) {
//阻止线程运行到duration秒
this_thread::sleep_for(chrono::seconds(duration));
//this_thread::get_id()获取当前线程id
cout << "exec_produce thread " << this_thread::get_id()
<< " has sleeped " << duration << " seconds" << endl;
}
int main(int argc, const char *argv[])
{
thread threads[5];
cout << "create 5 threads ..." << endl;
for (int i = 0; i < 5; i++) {
threads[i] = thread(exec_produce, i + 1);
}
cout << "finished creating 5 threads, and waiting for joining" << endl;
//下面代码会报错,原因就是copy操作不可用,相当于是delete操作,所以报错
/*for(auto it : threads) {
it.join();
}*/
for (auto& it: threads) {
it.join();
}
cout << "Finished!!!" << endl;
system("pause");
return 0;
}
其他相关函数作用说明
1)get_id() 获取线程 ID。
2)joinable() 检查线程是否可被 join
3)join() Join 线程。
4)detach() Detach 线程
5)swap() Swap 线程
6)native_handle() 返回 native handle。
7)hardware_concurrency() 检测硬件并发特性
4. std::mutex
mutex 又称互斥量,C++ 11中与 mutex 相关的类(包括锁类型)和函数都声明在 头文件中,所以如果需要使用 std::mutex,就必须包含 头文件,mutex中包含以下:
mutex系列类
std::mutex,最基本的 mutex类
std::recursive_mutex,递归 mutex类
std::time_mutex,定时 mutex类。
std::recursive_timed_mutex,定时递归 mutex类
lock 类
std::lock_guard,与 mutex RAII 相关,方便线程对互斥量上锁
std::unique_lock,与 mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制
其他类型
std::once_flag
std::adopt_lock_t
std::defer_lock_t
std::try_to_lock_t
函数
std::try_lock(),尝试同时对多个互斥量上锁。
std::lock(),可以同时对多个互斥量上锁。
std::call_once(),如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。
(1)std::mutex
std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。
std::mutex 的成员函数
1)构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
2)lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
3)unlock(), 解锁,释放对互斥量的所有权。
4)try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况,(1). 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(2). 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
/**
就像大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。
它是被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,
基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
**/
volatile int counter = 0;
const int MAX_TIMES_VALUE = 10000;
mutex my_mutex;
void my_task() {
for (int i = 0; i < MAX_TIMES_VALUE; ++ i) {
//尝试获取锁,try_lock()失败时返回false
if (my_mutex.try_lock()) {
++counter;
my_mutex.unlock();
}
}
}
int main() {
thread threads[10];
for (int i = 0; i < 10; ++ i) {
threads[i] = thread(my_task);
}
for (auto& it : threads) {
it.join();
}
cout << "Finished : the result of counter is " << counter << endl;
system("pause");
return 0;
}
(2)std::recursive_mutex
std::recursive_mutex 与 std::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。
(3)std::time_mutex
std::time_mutex 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until()
try_lock_for() 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
try_lock_until() 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;
timed_mutex my_mutex;
void my_task(int val, char tag) {
//每200ms尝试获取锁,如果获取到跳出while循环,否则输出一次线程编号
//比如0-200ms,在200ms之前如果获取不到锁,则线程阻塞,时间到了200ms如果取得了锁,
//则加锁,否则返回false
while (!my_mutex.try_lock_for(chrono::milliseconds(200))) {
//int pid = this_thread::get_id().hash();
cout << val;
}
//成功取得锁,然后将线程sleep到1000ms
this_thread::sleep_for(chrono::milliseconds(1000));
cout << tag << endl;
my_mutex.unlock();
}
int main ()
{
thread threads[10];
char end_tag[] = {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')'};
//创建10个线程,分别执行my_task()中的代码
for (int i=0; i<10; ++i) {
threads[i] = thread(my_task, i, end_tag[i]);
}
for (auto& it : threads) {
it.join();
}
system("pause");
return 0;
}
结果分析:
0-9号线程从0ms时刻开始运行,由于线程调度的随机性,假使最开始运行的是0号线程,则0号线程可以在0ms时刻便取得锁,这时候0号线程持锁sleep,这时线程调度会去执行1-9号线程,显然这时是无法取得锁的,所以什么调用try_lock_for()在0-200ms内去尝试取锁,在200ms之前由于取不到锁会分别阻塞,到了200ms这个时刻由于取锁失败,try_lock_for()返回false,所以在200ms这个时刻会在控制台中输出1-9这九个字符,之后类似的,直到到了1000ms这个时刻,0号线程释放了锁,并输出与其对应的tag。
之后的过程便成了9个线程的调度执行的过程了,和上面描述基本类似的过程。
(4)std::recursive_timed_mutex
和 std:recursive_mutex 与 std::mutex 的关系一样,std::recursive_timed_mutex 的特性也可以从 std::timed_mutex 推导出来
(5)std::lock_guard
与 mutex RAII 相关,方便线程对互斥量上锁,是一种自解锁。std::lock_guard是一个局部变量,创建时,对mutex 上锁,析构时对mutex解锁。这个功能在函数体比较长,尤其是存在多个分支的时候很有用
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex my_mutex;
void print (int x) {
cout << "value is " << x;
cout << endl;
this_thread::sleep_for(chrono::milliseconds(200));
}
void my_task (int id) {
// lock_guard创建局部变量my_lock,会在lock_guard的构造方法中对my_mutex加锁
lock_guard<mutex> my_lock (my_mutex);
//由于自解锁的作用,下面的代码相当于临界区,执行过程不会被打断
print(id);
//运行结束时会析构my_lock,然后在析构函数中对my_mutex解锁
}
int main ()
{
thread threads[10];
for (int i=0; i<10; ++i) {
threads[i] = thread(my_task,i+1);
}
for (auto& th : threads) {
th.join();
}
system("pause");
return 0;
}
(6)std::unique_lock
与 mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制,相对于std::lock_guard来说,std::unique_lock更加灵活,std::unique_lock不拥有与其关联的mutex。构造函数的第二个参数可以指定为std::defer_lock,这样表示在构造unique_lock时,传入的mutex保持unlock状态。然后通过调用std::unique_lock对象的lock()方法或者将将std::unique_lock对象传入std::lock()方法来锁定mutex。
std::unique_lock比std::lock_guard需要更大的空间,因为它需要存储它所关联的mutex是否被锁定,如果被锁定,在析构该std::unique_lock时,就需要unlock它所关联的mutex。std::unique_lock的性能也比std::lock_guard稍差,因为在lock或unlock mutex时,还需要更新mutex是否锁定的标志。大多数情况下,推荐使用std::lock_guard但是如果需要更多的灵活性,比如上面这个例子,或者需要在代码之间传递lock的所有权,这可以使用std::unique_lock,std::unique_lock的灵活性还在于我们可以主动的调用unlock()方法来释放mutex,因为锁的时间越长,越会影响程序的性能,在一些特殊情况下,提前释放mutex可以提高程序执行的效率。
使用std::unique_lock默认构造方法和std::lock_guard类似,多了可以主动unlock,其他相当于一个自解锁,所以类似于unique_lock my_lock(my_mutex)的用法就不再举例。
下面举例使用两个参数构造使用,和锁的所有权传递问题
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex my_mutex;
mutex some_mutex;
//使用含两个参数的构造函数
void my_task (int n, char c) {
unique_lock<mutex> my_lock (my_mutex, defer_lock);
my_lock.lock();
for (int i=0; i<n; ++i) {
cout << c;
}
cout << endl;
//会自动unlock
}
unique_lock<mutex> prepare_task()
{
unique_lock<mutex> lock(some_mutex);
cout << "print prepare data" << endl;
//返回对some_mutex的所有权,尚未解锁
return lock;
}
void finish_task(int v)
{
//取得prepare_task创建的锁所有权
unique_lock<mutex> lk(prepare_task());
cout << "finished :" << v << endl;
//析构,解锁
}
int main ()
{
thread t1 (my_task, 50, '1');
thread t2 (my_task, 50, '2');
t1.join();
t2.join();
thread threads[5];
for(int i = 0; i < 5; ++ i)
{
threads[i] = thread(finish_task, i);
}
for(auto& it : threads) {
it.join();
}
system("pause");
return 0;
}
其他不同构造函数使用方法简介
1)try-locking :unique_lock(mutex_type& m, try_to_lock_t tag)
新创建的 unique_lock 对象管理 mutex 对象 m,并尝试调用 m.try_lock() 对 mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。
2)deferred :unique_lock(mutex_type& m, defer_lock_t tag) noexcept
新创建的 unique_lock 对象管理 mutex 对象 m,但是在初始化的时候并不锁住 mutex 对象。m 应该是一个没有当前线程锁住的 mutex 对象。
3)adopting :unique_lock(mutex_type& m, adopt_lock_t tag)
新创建的 unique_lock 对象管理 mutex 对象 m, m 应该是一个已经被当前线程锁住的 mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。
4)locking for:template
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
int value;
once_flag value_flag;
void setValue (int x) {
value = x;
}
void my_task (int id) {
this_thread::sleep_for(chrono::milliseconds(1000));
//使setValue函数只被第一次执行的线程执行
call_once (value_flag, setValue, id);
}
int main ()
{
thread threads[10];
for (int i=0; i<10; ++i) {
threads[i] = thread(my_task,i+1);
}
for (auto& it : threads){
it.join();
}
cout << "Finished!! the result of value is : " << value << endl;
system("pause");
return 0;
}
5. std::promise
promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。
可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)
1)promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
2)future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。
入门示例
#include <iostream>
#include <functional>
#include <thread>
#include <future>
using namespace std;
//通过std::future获取共享状态的值
void printShareState(future<int>& state) {
// 获取共享状态的值.
int x = state.get();
cout << "share state value : " << x << endl;
}
int main ()
{
// 创建一个 promise<int> 对象,状态值为int类型
promise<int> prom;
// 和 future 关联
future<int> fut = prom.get_future();
// 将 future 交给另外一个线程t.
thread t(printShareState, ref(fut));
// 设置共享状态的值, 此处和线程t保持同步.
prom.set_value(10);
t.join();
system("pause");
return 0;
}
(1)构造函数
1)默认构造函数promise(),初始化一个空的共享状态。
2)带自定义内存分配器的构造函数template promise (allocator_arg_t aa, const Alloc& alloc),与默认构造函数类似,但是使用自定义分配器来分配共享状态。
3)拷贝构造函数promise (const promise&) = delete,被禁用。
4)移动构造函数promise (promise&& x) noexcept。
另外,std::promise 的 operator= 没有拷贝语义,即 std::promise 普通的赋值操作被禁用,operator= 只有 move 语义,所以 std::promise 对象是禁止拷贝的
#include <iostream>
#include <thread>
#include <future>
using namespace std;
//使用默认构造函数构造一个空共享状态的promise对象
promise<int> prom;
void printShareStateValue () {
future<int> fut = prom.get_future();
int x = fut.get();
cout << "share state value is " << x << endl;
}
int main ()
{
thread t1(printShareStateValue);
prom.set_value(10);
t1.join();
//promise<int>()创建一个匿名空的promise对象,使用移动拷贝构造函数给prom
prom = promise<int>();
thread t2 (printShareStateValue);
prom.set_value (20);
t2.join();
system("pause");
return 0;
}
(2)其他成员函数
1)std::promise::get_future()
该函数返回一个与 promise 共享状态相关联的 future 。返回的 future 对象可以访问由 promise 对象设置在共享状态上的值或者某个异常对象。只能从 promise 共享状态获取一个 future 对象。在调用该函数之后,promise 对象通常会在某个时间点准备好(设置一个值或者一个异常对象),如果不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error 异常(broken_promise)来设置其自身的准备状态
2)std::promise::set_value()
void set_value (const T& val);
void set_value (T&& val);
void promise
#include <iostream>
#include <functional>
#include <thread>
#include <future>
#include <exception>
using namespace std;
void getAnInteger(promise<int>& prom) {
int x;
cout << "input an integer : ";
//设置如试图从不能解析为整数的字符串里想要读一个整数等,顺便说下eof也会造成failbit被置位
//则产生异常
cin.exceptions (ios::failbit);
try {
cin >> x;
prom.set_value(x);
} catch (exception&) {
prom.set_exception(current_exception());
}
}
void printAnInteger(future<int>& fut) {
try {
int x = fut.get();
cout << "the value of Integer is " << x << endl;
} catch (exception& e) {
cout << "exception content:{ " << e.what() << " }" << endl;;
}
}
int main ()
{
promise<int> prom;
future<int> fut = prom.get_future();
thread t1(getAnInteger, ref(prom));
thread t2(printAnInteger, ref(fut));
t1.join();
t2.join();
system("pause");
return 0;
}
6. std::packaged_task
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 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
using namespace std;
//
int my_task (int from, int to) {
for (int i = from; i != to; --i) {
cout << i << endl;
this_thread::sleep_for(chrono::seconds(1));
}
cout << "Finished!!" << endl;
return from - to;
}
int main ()
{
// 设置packaged_task,形式上类似于std::function
// 如std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
packaged_task<int(int,int)> task(my_task);
// 获得与 packaged_task 共享状态相关联的 future 对象.
future<int> ret = task.get_future();
thread th(move(task), 10, 0); //创建一个新线程完成计数任务.
int value = ret.get(); // 等待任务完成并获取结果.
cout << "the result of the future state value is " << value << endl;
th.join();
system("pause");
return 0;
}
(1)std::packaged_task 构造函数
1)默认构造函数packaged_task() noexcept,初始化一个空的共享状态,并且该 packaged_task 对象无包装任务。
2)template explicit packaged_task (Fn&& fn),初始化一个共享状态,并且被包装任务由参数 fn 指定。
3)带自定义内存分配器的构造函数template
#include <iostream>
#include <utility>
#include <future>
#include <thread>
using namespace std;
int main ()
{
// 默认构造函数.
packaged_task<int(int)> foo;
// 使用 lambda 表达式初始化一个 packaged_task 对象.
packaged_task<int(int)> bar([](int x){return x * 3;});
foo = move(bar); // move-赋值操作,也是 C++11 中的新特性.
// 获取与 packaged_task 共享状态相关联的 future 对象.
future<int> ret = foo.get_future();
thread(move(foo), 30).detach(); // 产生线程,调用被包装的任务.
int value = ret.get(); // 等待任务完成并获取结果.
cout << "The final result is " << value << ".\\n";
system("pause");
return 0;
}
(2)std::packaged_task::valid() 检查当前 packaged_task 是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task 对象,该函数返回 false,除非中间进行了 move 赋值操作或者 swap 操作
#include <iostream>
#include <utility>
#include <future>
#include <thread>
using namespace std;
// 在新线程中启动一个 int(int) packaged_task.
future<int> launcher(packaged_task<int(int)>& tsk, int arg)
{
if (tsk.valid()) {
future<int> ret = tsk.get_future();
//创建匿名线程,线程运行结束后直接退出不需要和主线程会合
thread (move(tsk),arg).detach();
return ret;
}
else return future<int>();
}
int main ()
{
packaged_task<int(int)> tsk([](int x){return x*2;});
future<int> fut = launcher(tsk,25);
if(fut.get() == 50) {
cout << "task is valid refrence to a shared state" << endl;
} else {
cout << "do nothing" << endl;
}
system("pause");
return 0;
}
(3)std::packaged_task::get_future()
返回一个与 packaged_task 对象共享状态相关的 future 对象。返回的 future 对象可以获得由另外一个线程在该 packaged_task 对象的共享状态上设置的某个值或者异常
(4)std::packaged_task::operator()(Args… args)
调用该 packaged_task 对象所包装的对象(通常为函数指针,函数对象,lambda 表达式等),传入的参数为 args. 调用该函数一般会发生两种情况:
如果成功调用 packaged_task 所包装的对象,则返回值(如果被包装的对象有返回值的话)被保存在 packaged_task 的共享状态中。
如果调用 packaged_task 所包装的对象失败,并且抛出了异常,则异常也会被保存在 packaged_task 的共享状态中。
以上两种情况都使共享状态的标志变为 ready,因此其他等待该共享状态的线程可以获取共享状态的值或者异常并继续执行下去。
共享状态的值可以通过在 future 对象(由 get_future获得)上调用 get 来获得。
由于被包装的任务在 packaged_task 构造时指定,因此调用 operator() 的效果由 packaged_task 对象构造时所指定的可调用对象来决定:
如果被包装的任务是函数指针或者函数对象,调用 std::packaged_task::operator() 只是将参数传递给被包装的对象。
如果被包装的任务是指向类的非静态成员函数的指针,那么 std::packaged_task::operator() 的第一个参数应该指定为成员函数被调用的那个对象,剩余的参数作为该成员函数的参数。
如果被包装的任务是指向类的非静态成员变量,那么 std::packaged_task::operator() 只允许单个参数
(5)std::packaged_task::make_ready_at_thread_exit()
该函数会调用被包装的任务,并向任务传递参数,类似 std::packaged_task 的 operator() 成员函数。但是与 operator() 函数不同的是,make_ready_at_thread_exit 并不会立即设置共享状态的标志为 ready,而是在线程退出时设置共享状态的标志。
如果与该 packaged_task 共享状态相关联的 future 对象在 future::get 处等待,则当前的 future::get 调用会被阻塞,直到线程退出。而一旦线程退出,future::get 调用继续执行,或者抛出异常。
注意,该函数已经设置了 promise 共享状态的值,如果在线程结束之前有其他设置或者修改共享状态的值的操作,则会抛出 future_error( promise_already_satisfied )
(6)std::packaged_task::swap()
交换 packaged_task 的共享状态
(7)std::packaged_task::reset()
重置 packaged_task 的共享状态,但是保留之前的被包装的任务
#include <iostream>
#include <utility>
#include <future>
#include <thread>
using namespace std;
int tripleX (int x)
{
return x*3;
}
int main ()
{
packaged_task<int(int)> tsk (tripleX);
future<int> fut = tsk.get_future();
//thread t1(move(tsk), 100);
tsk(100);
cout << "The triple of 100 is " << fut.get() << endl;
//t1.join();
//重置tsk的共享状态
//this_thread::sleep_for(chrono::milliseconds(500));
tsk.reset();
fut = tsk.get_future();
thread(move(tsk), 200).detach();
cout << "Thre triple of 200 is " << fut.get() << endl;
system("pause");
return 0;
}
附:std::function使用实例
直接附代码
#include <functional>
#include <iostream>
using namespace std;
std::function< int(int)> Functional;
// 普通函数
int TestFunc(int a)
{
return a;
}
// Lambda表达式
auto lambda = [](int a)->int{ return a; };
// 仿函数(functor)
class Functor
{
public:
int operator()(int a)
{
return a;
}
};
// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
int ClassMember(int a) { return a; }
static int StaticMember(int a) { return a; }
};
int main()
{
// 普通函数
Functional = TestFunc;
int result = Functional(10);
cout << "普通函数:"<< result << endl;
// Lambda表达式
Functional = lambda;
result = Functional(20);
cout << "Lambda表达式:"<< result << endl;
// 仿函数
Functor testFunctor;
Functional = testFunctor;
result = Functional(30);
cout << "仿函数:"<< result << endl;
// 类成员函数
TestClass testObj;
Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
result = Functional(40);
cout << "类成员函数:"<< result << endl;
// 类静态函数
Functional = TestClass::StaticMember;
result = Functional(50);
cout << "类静态函数:"<< result << endl;
system("pause");
return 0;
}
附:C++11 lambda表达式使用实例
盗个图表示一下lambda表达式的语法
1 表示Lambda表达式的引入标志,在‘[]’里面可以填入‘=’或‘&’表示该lambda表达式“捕获”(lambda表达式在一定的scope可以访问的数据)的数据时以什么方式捕获的,‘&’表示一引用的方式;‘=’表明以值传递的方式捕获,除非专门指出。
2 表示Lambda表达式的参数列表
3 表示Mutable 标识
4 表示异常标识
5 表示返回值
6 表示“函数”体,也就是lambda表达式需要进行的实际操作
实例可参考上例中的:
auto lambda = [](int a)->int{ return a; };
7. std::future
std::future 可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段。std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值,如果共享状态的标志不为 ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。
一个有效(valid)的 std::future 对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,其实我们前面都已经提到了,他们分别是:
1)std::async() 函数,本文后面会介绍 std::async() 函数。
2)std::promise::get_future(),get_future 为 promise 类的成员函数
3)std::packaged_task::get_future(),此时 get_future为 packaged_task 的成员函数。
一个 std::future 对象只有在有效(valid)的情况下才有用(useful),由 std::future 默认构造函数创建的 future 对象不是有效的(除非当前非有效的 future 对象被 move 赋值另一个有效的 future 对象)。
在一个有效的 future 对象上调用 get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值或异常(此时共享状态的标志变为 ready),std::future::get 将返回异步任务的值或异常(如果发生了异常)。
#include <iostream>
#include <future>
#include <chrono>
using namespace std;
bool is_prime(int x)
{
for (int i = 2; i < x; ++i) {
if (x % i == 0) {
return false;
}
}
return true;
}
int main()
{
// 异步调用判断999999是不是质数
future < bool > fut = async(is_prime, 9999999999);
//由于上面代码中判断素数的方法对于比较大的数,将耗费较多时间
//开始判断共享状态是否变为了ready,等待
cout << "waiting for ready..." << endl;
chrono::milliseconds span(100);
//每100ms内阻塞等待,如果超时输出*,并继续等待,否则结束while
while (fut.wait_for(span) == future_status::timeout)
cout << '*';
bool ret = fut.get();
cout << endl;
cout << "9999999999 " << (ret ? "is" : "is not") << " a prime." << endl;
system("pause");
return 0;
}
(1)构造函数
1)默认构造函数future() noexcept
2)复制构造函数future (const future&) = delete,不可用
3)future (future&& x) noexcept,move构造函数
(2)赋值操作 普通赋值=已被重载为move操作
(3)std::future::share()
返回一个 std::shared_future 对象,调用该函数之后,该 std::future 对象本身已经不和任何共享状态相关联,因此该 std::future 的状态不再是 valid 的了。
#include <iostream>
#include <future>
using namespace std;
int do_get_value() {
return 10;
}
int main ()
{
future<int> fut = async(do_get_value);
shared_future<int> shared_fut = fut.share();
// 共享的 future 对象可以被多次访问, fut的状态到这里便不再是valid的了
cout << "value: " << shared_fut.get() << '\\n';
cout << "its double: " << shared_fut.get()*2 << '\\n';
system("pause");
return 0;
}
(4)std::future::get()
1)T get();
2)R& future
#include <iostream>
#include <functional>
#include <thread>
#include <future>
#include <exception>
using namespace std;
void getAnInteger(promise<int>& prom) {
int x;
cout << "input an integer : ";
//设置如试图从不能解析为整数的字符串里想要读一个整数等,顺便说下eof也会造成failbit被置位
//则产生异常
cin.exceptions (以上是关于C++并发编程(C++11)的主要内容,如果未能解决你的问题,请参考以下文章