创建线程
#include<iostream>
#include<thread>
using namespace std;
void myPrint()
{
cout<<"myPrint()线程开始执行"<<endl;
cout<<"myPrint()线程执行完毕"<<endl;
return;
}
class TA
{
public:
TA()
{
cout<<"TA的构造函数"<<endl;
}
TA(const TA &ta)
{
cout<<"TA的拷贝构造函数"<<endl;
}
~TA()
{
cout<<"TA的析构函数"<<endl;
}
void operator()()// 作为可调用对象必须重载()运算符
{
cout<<"operator()线程开始执行"<<endl;
cout<<"operator()线程执行完毕"<<endl;
return;
}
void thread_work()
{
cout<<"TA::thread_work()线程开始执行"<<endl;
cout<<"TA::thread_work()线程执行完毕"<<endl;
return;
}
};
int main()
{
// 1使用函数创建线程
thread myThread1(myPrint);// 创建线程、线程执行入口、线程开始执行
if(myThread1.joinable())
{
myThread1.join();// 阻塞主线程,让主线程等待子线程执行完毕,然后主线程和子线程汇合,主线程继续往下运行
//myThread1.detach();// 分离主线程,主线程运行结束将不再结束子线程,子线程将在后台继续运行
}
// 2使用可调用类对象创建线程
TA ta;
thread myThread2(ta);// 注意这里传值
if(myThread2.joinable())
{
myThread2.join();
//myThread2.detach();
}
// 3使用类成员函数创建线程
thread myThread3(&TA::thread_work, std::ref(ta));// 注意这里传引用
if(myThread3.joinable())
{
myThread3.join();
//myThread.detach();
}
// 4使用lambda表达式创建线程
auto lamThread=[]{
cout<<"lamThread线程开始执行"<<endl;
cout<<"lamThread线程执行完毕"<<endl;
};
thread myThread4(lamThread);
if(myThread4.joinable())
{
myThread4.join();
//myThread4.detach();
}
cout<<"hello world!"<<endl;
return 0;
}
上面代码输出如下:
myPrint()线程开始执行
myPrint()线程执行完毕
TA的构造函数
TA的拷贝构造函数
operator()线程开始执行
operator()线程执行完毕
TA的析构函数
TA::thread_work()线程开始执行
TA::thread_work()线程执行完毕
lamThread线程开始执行
lamThread线程执行完毕
hello world!
TA的析构函数
- 调用拷贝构造函数说明:类对象在初始化线程时,会被复制到线程中
- 第一个析构函数是线程中被复制的TA对象的析构,第二个析构函数是主线程中的ta的析构
- 使用std::ref(),可以真正实现引用的传递,并且减少了线程中对象的拷贝。
线程传参
传递临时对象作为线程参数
void myPrint(const int &i, char *pBuff)// 传递引用和指针
{
cout<<i<<endl;
cout<<pBuff<<endl;
return;
}
int main()
{
int mvar=1;
int &mvary=mvar;
char mBuff[]="this is a test";
thread myThread(myPrint, mvar, mBuff);
myThread.join();
cout<<"hello world!"<<endl;
return 0;
}
-
实际上述代码存在问题:
- mvar的地址=0x0022fa58,&mvary=0x0022fa58,而myPrint函数中i的地址=0x0072d9ec。也就是说,线程函数中的引用形参i实际上是值传递,并不是引用传递。
- main函数中mBuff的地址=0x004ffb64,myPrint函数中pBuff的地址=0x004ffb64,说明线程函数中的指针形参与传递的实参指向同一块内存。
- 若main函数中,线程被detach后,主线程运行结束,临时变量会被销毁,此时线程函数中的引用参数i由于是被复制进来的,仍然可以安全使用。但是指针参数pBuff则会变为野指针。
-
将上述代码修改如下:
void myPrint(const int &i, const string &pBuff)// 这里得是const
{
cout<<i<<endl;
cout<<pBuff<<endl;
return;
}
int main()
{
int mvar=1;
int &mvary=mvar;
char mBuff[]="this is a test";
thread myThread(myPrint, mvar, mBuff);
myThread.join();
cout<<"hello world!"<<endl;
return 0;
}
-
实际上上述代码仍然存在问题:
- 事实上,main函数中的char mBuff在隐式转换为string时,有可能是在主线程运行结束后进行的,导致线程函数中的pBuff不一定是mBuff中的值。
-
正确写法如下:
void myPrint(const int &i, const string &pBuff)
{
cout<<i<<endl;
cout<<pBuff<<endl;
return;
}
int main()
{
int mvar=1;
int &mvary=mvar;
char mBuff[]="this is a test";
thread myThread(myPrint, mvar, string(mBuff));
myThread.join();
cout<<"hello world!"<<endl;
return 0;
}
- 因此要使用临时对象作为线程参数,避免隐式类型转换。证明如下:
class A
{
public:
int m_i;
A(int a) :m_i(a) { cout << "A的构造函数" << this << endl; }
A(const A& a) :m_i(a.m_i) { cout << "A的拷贝构造函数" << this << endl; }
~A() { cout << "A的析构函数" << this << endl; }
};
void myPrint(const A& a)
{
cout << a.m_i << endl;
return;
}
int main()
{
//thread myThread(myPrint, 10);// 使用隐式转换
thread myThread(myPrint, A(10));// 使用临时对象
myThread.detach();
return 0;
}
-
若使用隐式转换,则程序并没有输出A的构造函数,即并未在main函数结束前进行隐式转换,子线程不安全。
-
若使用临时对象,则程序输出如下:
A的构造函数004FF8A0 A的拷贝构造函数008CD6F0 A的析构函数004FF8A0
表明,在main函数结束前,已经构造出临时对象。此外,还通过拷贝构造函数,将临时对象传递给了线程函数。传递引用时,传入的是值的副本,也就是说子线程中的修改影响不了主线程中的值。
总结
- 若传递int这种简单类型参数,建议值传递
- 如果传递类对象,避免隐式类型转换,应使用临时对象传参。且在线程函数中使用引用传递,避免二次拷贝。
- 建议不使用detach,使用join,避免局部变量失效,导致线程对内存的非法引用问题。
传递类对象、智能指针
class A
{
public:
int m_i;
A(int a) :m_i(a) { cout << "A的构造函数" << this << endl; }
A(const A& a) :m_i(a.m_i) { cout << "A的拷贝构造函数" << this << endl; }
~A() { cout << "A的析构函数" << this << endl; }
};
void myPrint(A& a, shared_ptr<int> p)
{
cout << a.m_i << endl;
cout << *p << endl;
return;
}
int main()
{
A a(10);
shared_ptr<int> p(new int(100));
thread myThread(myPrint, std::ref(a), p);// 传递类的引用和智能指针
myThread.join();
//myThread.detach();
return 0;
}
- 使用std::ref(),可以真正实现引用的传递,并且减少了线程中对象的拷贝。