C++11后可以通过提供的线程类thread进行线程创建,实现线程跨平台
#include <iostream>
#include <thread> //包含线程头文件
using namespace std;
//线程要执行的任务,或者说入口
void myprint(int a)
{
cout << a << "线程开始执行" << endl;
}
class myclass
{
public:
bool greater(int a,int b)
{
return a > b;
}
void myprint(int a)
{
cout << a << "线程开始执行" << endl;
}
void operator() (int a)
{
cout << a << "线程开始执行" << endl;
}
};
int main()
{
//通过实例化thread构建线程并开始执行,第一个参数为可执行对象
//可以是普通函数,函数指针,仿函数,lambda表达式,使用bind构造的可调用对象
//之后的参数是线程入口函数的参数
//普通函数做线程入口函数,参数2
thread mythread(myprint,2);
//lambda表达式做线程入口函数,参数10
thread mythread2([](int p){
cout << p << "线程开始执行" << endl;
},10);
//类成员函数做线程入口函数,参数3
myclass m;
//函数 类对象 参数
thread mythread3(&myclass::myprint, &m ,3); //使用m的myprint函数,也可以ref(m)
thread mythread3(&myclass::myprint, m ,3); //线程复制一个myclass对象,调用了它的myprint
//通过bind将类成员函数转成可调用对象
thread mythread3(bind(&myclass::myprint, &m, placeholders::_1),3);
//仿函数做线程入口函数,参数6
thread mythread4(m ,6); //主线程调用拷贝构造函数复制一个myclass对象到子线程
thread mythread4(ref(m) ,6); //使用主线程局部变量m
//线程创建完成后开始执行,我们还需要指定其与主线程的先后关系
//线程join()使得主线程要等待子线程完成后才能继续执行,是同步关系
//线程detach()使主线程不等待子线程,可能造成主线程执行完但子线程还没执行完
//这时候主线程的局部变量可能失效了,导致不可预料的后果!需要谨慎使用
mythread.join();
mythread2.join();
mythread3.join();
mythread4.join();
//join和detach只能有一个,且必须指定一个
//线程还有几个比较重要的函数
mythread.get_id(); //获取线程id
mythread.swap(mythread2); //交换两个线程的执行内容
mythread.joinable(); //检查线程是否可以被join或detach
}
使用detach时需要避免的问题
1.函数不使用指针做参数
2.函数使用引用作为参数,但是线程默认还是值传递,除非使用ref指定才能进行真引用
void myprint(string &s) //真引用
{
s+="aaa";
cout << s << endl;
}
int main()
{
string a = "abc",b="bcd";
thread mythread(myprint,ref(a)); //真引用
thread mythread2(myprint,b); //还是值传递
mythread.detach();
mythread2.detach();
}
3.使用string& 接收主线程的 char指针指向的数据 可能会有主线程执行完,但是char指针的数据还没有转成string参数,导致出错
void myprint(const string &s) //要用引用接收,不然系统多一次拷贝构造,(变成两次拷贝构造)
{ //虽然用了引用,但系统默认会用值传递,所以传参时调用拷贝构造
cout << s << endl;
}
int main()
{
char a[] = "abc";
// thread mythread(myprint,a); //a转string是在子线程执行的,编译时看到这一行只压栈
//主线程调用string构造函数
thread mythread(myprint,string(a)); //编译时要先将a转string,此时会顺便调用拷贝构造,形成myprint的实参,压栈,不会发生要转换时已经失效
mythread.detach();
}
除了thread外,还有另外一个async,这个是创建异步任务。
std::async配合std::future使用
async创建异步任务,用法和thread类似,返回值用future接收,这个future的值在未来某时间可以使用get()成员函数获取 (线程执行完毕时)
第一个参数是启动方式,使用launch::async ,代表创建异步任务并开始执行(即 开一个子线程,并执行线程入口函数) ;另一个启动方式是launch::deferred,代表延迟调用,使得某个线程在调用future的get()或wait()成员时,执行线程入口函数。
如果启动方式缺省,那么行为是由系统决定的,可能是async方式,也可能是deferred
相当于用 launch::async | launch::deferred (一般在资源足够时自动用async,不够时用deferred)
第二个及后续参数是可调用对象和所传的参数,同thread
#include<future>
int func (int a)
{
cout << a << endl;
cout << "thread_id=" << this_thread::get_id() << endl;
}
int main()
{
future<int> result = async(launch::async,func,3); //创建异步任务并开始执行
future<int> result1 = async(launch::deferred,[]{
return 10;
}); //创建异步任务,延迟执行
//future<int> result2 = async(func,3); 行为未知
// == future<int> result2 = async(launch::async | launch::deferred,func,3);
//async的成员函数get()获取异步任务执行结果(返回值),如果已经执行完就直接获取,
//不然就在此行阻塞等待任务完成再获取 只能执行一次get get后不能再wait或get
//因为get的设计是一个移动语义move,future保存的数据被move走了
//如果需要多次使用,可以考虑shared_future,后文会提到
//wait()成员函数阻塞等待任务执行完成,但不获取结果
//如果是以async方式启动,那么主线程执行完也会自动等待异步任务完成,最后再退出
//deferred方式如果没有wait或get那么就不执行
result.wait();
cout << result1.get();
}
如果使用缺省或 launch::async | launch::deferred 方式调用async,那么编程时需要考虑两种方式下该怎么执行。
future的wait_for函数返回了三种状态,可以通过这个状态判断async是哪种执行方式,相应做出不同操作,后面会介绍这三种
void func()
{
}
int main()
{
future<int> result = async(func);
future_status s = result.wait_for(chrono::seconds(0)); //wait_for(0s)也是可以的写法
if(s == future_status::deferred ) //deferred方式,且还没有开始执行
{
result.wait();//或者get //在此线程开始执行async创建的任务
}
else //执行完成或者还在执行
{
//....
}
}
什么时候用async
使用thread会直接开始执行,执行完就销毁线程,下次再调用大概率是创建新线程。其接收返回值比较麻烦
async可以控制立即执行或延迟执行,而且接收返回值可以使用future。同时async创建异步任务,自然涉及到线程,它大概率是使用线程池,多次调用可能会复用线程
除了async,还可以通过包装线程入口函数来获取返回值
使用std::packaged_task包装线程入口函数,包装后可以用future获取返回值,配合thread ,future使用
class Work
{
public:
int operator() (int a)
{
return a+10;
}
int func(int a)
{
return a+20;
}
}
int main()
{
Work w;
//传入一个返回值int,参数int的可调用对象
packaged_task<int(int)> pt(w);
//将类成员函数绑定成function传给packaged_task
packaged_task<int(int)> pt(bind(&Work::func,&w));
thread mythread(ref(pt),2);
mythread.join();
future<int> result = pt.get_future();
cout << result.get() << endl;
//包装后的packaged_task也可以直接当成函数调用
//pt(2);
}
future的其他成员函数
int func (int a)
{
cout << a << endl;
cout << "thread_id=" << this_thread::get_id() << endl;
}
int main()
{
future<int> result = async(func,3);
std::shared_future s_result = result.share(); //转换成shared_future
result.valid(); //future是否有效(是否绑定某个东西)
result.wait_for(chrono::milliseconds(100)); //阻塞等待一段时间后返回future状态
//future状态有三种 future_status::ready 是可以获取值了
//future_status::time_out 超时,规定时间内没有变ready
//future_status::deferred 线程还没有开始执行(创建时用了deferred方式)
}
由于future使用get获取值是移动语义实现,我们需要在多处使用get的时候,应该选择std::shared_future
它的get是复制一份,可以多次调用
//构造async返回future,使用future直接构造shared_future
shared_future<int> result = async(launch::deferred,test);
shared_future<int> result(async(launch::deferred,test));
//根据已有的future构建shared_future,原有的future被move
shared_future<int> res(result.share());
shared_future<int> res(move(result));
shared_future<int> res = move(result);
cout << res.get() << endl;
cout << res.get() << endl;
线程间通信
std::promise提供了线程间通信的手段,类似于传引用,但是其配合future确保数据被写之后才能被另一个线程获取
一般在异步调用时使用,主线程将复杂计算任务交给子线程,然后自己继续执行,后续需要获取计算任务结果时再通过和promise绑定的future获取,能确保获取到计算后的结果,而不是还没计算完成 就被获取。
void mytask(promise<int>& p, int a)
{
a *= 6;
a++;
p.set_value(a);
}
void use_val(future<int>& f)
{
cout << f.get() << endl;
}
int main()
{
promise<int> p; //创建promise对象
future<int> result = p.get_future(); //future和它绑定
thread mythread(mytask, ref(p),10); //执行复杂计算任务,通过传promise引用保存到promise
mythread.detach();
//不等待计算线程执行完毕,直接往下执行
//后续代码...
thread mythread2(use_val, ref(result)); //另一个任务需要计算结果,传future的引用
mythread2.join();
return 0;
}