1. 示例演示线程运行的开始和结束
程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
#include <iostream>
using namespace std;
int main()
{
cout<<"I Love China"<<endl;//实际上这是主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
return 0;
}
主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始执行(初始函数)。这个函数运行完毕,就代表这个线程运行结束。**整个进程是否执行完毕的标志是主线程是否执行完毕,如果主线程执行完毕就代表整个进程执行完毕。此时,一般情况下如果其他子线程还没有执行完毕,那么这些子线程也会被操作系统强行终止。**所以,一般情况下,如果想保持子线程(自己用代码创建的线程)的运行状态,那么就要让主线程保持运行,不要让它运行完毕。但是这条规律有例外,后期将解释这种例外。
创建多线程的步骤:
1.包含头文件thread库
2.自己创建一个线程运行的函数(初始函数,以myprint为例)
3.main中开始写线程调用代码
#include <iostream>
#include <thread>
using namespace std;
void myprint()
{
cout << "我的线程开始执行了!" << endl;
//...
cout << "我的线程执行结束了!" << endl;
}
int main()
{
thread mytrdobj(myprint);//生成一个属于thread类的对象
mytrdobj.join();
cout << "I Love China!" << endl;
return 0;
}
输出结果如下:
此时程序中有两个线程(主线程及自己创建的子线程)同时在跑,即使一条线程堵住了也不会影响另一条线程的运行。
1.1 thread
是标准库的一个类,thread实例化mytrdobj的形参myprint实际上是可调用对象。
thread mytrdobj(myprint);//创建了线程,线程执行入口是myprint();myprint()函数开始执行
1.2 join
阻塞主线程,让主线程等待子线程执行完毕然后子线程和主线程汇合,然后主线程再往下执行。
mytrdobj.join();//主线程阻塞到这里等待myprint()执行完,当子线程执行完毕join就执行完毕,主线程停止阻塞。
注释掉程序的这句代码,会得到下图,子线程未执行完主线程就继续执行导致输出混乱。程序报错原因是因为主线程未等待子线程执行结束就退出。子线程被系统kill掉。
一个传统的书写良好的程序是主线程等待子线程执行完毕后自己才能退出。
1.3 detach
传统的多线程程序主线程要等待子线程执行完毕然后自己再退出。C++11标准中detach(分离)方法打破了这一束缚,主线程可以不和子线程汇合。主线程可以比子线程先结束,这并不影响子线程的执行。
为什么引入detach?
我们创建了很多子线程,但是让主线程逐个等待子线程结束这种编程方法并不好。所以引入了detach()。
一旦detach之后,与主线程相关的thread对象就会失去与主线程的关联。此时子线程就会驻留在后台运行,与该主线程失去联系,相当于被C++运行时库接管。子线程运行结束后,由运行时库负责清理该线程相关资源(守护线程)。
detach使用举例,程序每次执行结果都不一样:
#include <iostream>
#include <thread>
using namespace std;
void myprint()
{
cout << "我的线程开始执行了!" << endl;
//...
cout << "\n子线程输出1" << endl;
cout << "\n子线程输出2" << endl;
cout << "\n子线程输出3" << endl;
cout << "\n子线程输出4" << endl;
cout << "\n子线程输出5" << endl;
cout << "\n子线程输出6" << endl;
cout << "\n子线程输出7" << endl;
cout << "\n子线程输出8" << endl;
}
int main()
{
thread mytrdobj(myprint);//创建了线程,线程执行入口是myprint();myprint()函数开始执行
mytrdobj.detach();//使线程myprint失去我们自己的控制。一旦调用了detach,就不能再用join。否则系统会汇报异常。
cout << "\n主线程输出1" << endl;
cout << "\n主线程输出2" << endl;
cout << "\n主线程输出3" << endl;
cout << "\n主线程输出4" << endl;
return 0;
}
可以看到,程序没有报错,经过detach后主线程与子线程各走各的,互不影响。第一张图输出结果很完美,在主线程执行return0之前,也就是主线程退出之前(进程退出之前),子线程已经把myprint函数全部执行完。但是第二张图子线程没输出完全。这是因为主线程先输出完毕,并且在到达return0后结束掉了进程。此时子线程变成了守护线程,仍然在后台执行,但是由于进程退出的原因守护线程已无法输出结果到屏幕。
建议还是遵循传统的多线程书写方法,detach很会出问题的。
1.4 joinable
判断是否可以成功使用join()或detach()。返回True或者False,表示现阶段能否被join()或detach()。
#include <iostream>
#include <thread>
using namespace std;
void myprint()
{
cout << "我的线程开始执行了!" << endl;
//...
cout << "\n子线程输出1" << endl;
cout << "\n子线程输出2" << endl;
cout << "\n子线程输出3" << endl;
cout << "\n子线程输出4" << endl;
cout << "\n子线程输出5" << endl;
cout << "\n子线程输出6" << endl;
cout << "\n子线程输出7" << endl;
cout << "\n子线程输出8" << endl;
}
int main()
{
thread mytrdobj(myprint);//创建了线程,线程执行入口是myprint();myprint()
if (mytrdobj.joinable())
{
cout << "\n主线程阻塞前允许阻塞" << endl;
}
else
{
cout << "\n主线程阻塞前不允许阻塞" << endl;
}
mytrdobj.join();
if (mytrdobj.joinable())
{
cout << "\n主线程阻塞后允许阻塞" << endl;
}
else
{
cout << "\n主线程阻塞后不允许阻塞" << endl;
}
cout << "\n主线程输出1" << endl;
cout << "\n主线程输出2" << endl;
cout << "\n主线程输出3" << endl;
cout << "\n主线程输出4" << endl;
return 0;
}
可见创建thread mytrdobj后是允许主线程阻塞的,但是阻塞完成后是不允许的。
2. 其他创建线程的手法
2.1 类对象
用类对象创建线程写法如下:
#include <iostream>
#include <thread>
using namespace std;
class TA {
public:
void operator()()//用类创建一个调用对象,不能带参数
{
cout << "我的线程开始执行了!" << endl;
//....
cout << "我的线程结束执行了!" << endl;
}
};
int main()
{
TA ta;
thread myclsobj(ta);//ta 可调用类对象
myclsobj.join();// 等待子线程执行结束
cout << "\n主线程执行结束" << endl;
return 0;
}
输出如下:
注意以下存在bug的代码
可以看到,不仅每次输出不一样,而且输出混乱。
#include <iostream>
#include <thread>
using namespace std;
class TA {
public:
int &mi;
TA(int &i) :mi(i) {}
void operator()()//用类创建一个调用对象,不能带参数
{
cout << "mi1的值为!" << mi << endl;
cout << "mi2的值为!" << mi << endl;
cout << "mi3的值为!" << mi << endl;
cout << "mi4的值为!" << mi << endl;
cout << "mi5的值为!" << mi << endl;
cout << "mi6的值为!" << mi << endl;
}
};
int main()
{
int myi = 6;
TA ta(myi);
thread myclsobj(ta);//ta 可调用类对象
myclsobj.detach();// 等待子线程执行结束
cout << "\n主线程执行结束" << endl;
return 0;
}
出现这种情况的原因是:TA类中引用变量m_i绑定的变量是i,这个i是构造类时传递进来的,也就是myi=6。由于使用了detach,主线程与子线程之间互不干扰。若主线程先执行完,则局部变量myi销毁,此时子线程还没有执行完,但是依赖的myi已经不存在,所以会出现输出混乱,产生不可预料的结果。解决的办法要么换成join方法,要么将类的成员变量mi由引用改为整型,这样即使myi被销毁mi还依然存在。
还存在一个问题:
ta作为一个局部变量,在主线程结束的时候也被销毁,为什么不影响子线程调用?
**因为这个ta对象实际上是被复制到子线程中去了。所以执行完主线程后,ta会被销毁,但是子线程中复制的ta对象依旧存在。**可以用以下代码作为验证(已经更改了上个bug):很明显在TA ta(myi)行 ta对象被创建,调用了构造函数。而thread myclsobj(ta)行 ta对象被拷贝了,调用了拷贝构造函数。最后调用两次析构函数(先析构拷贝构造的对象,再析构构造的ta对象)将两个对象销毁。这次运行结果比较好了,至少可以看到析构两次,若是主线程先退出,另一次析构可能还没来得及输出进程就结束了。
#include <iostream>
#include <thread>
using namespace std;
class TA {
public:
int mi;
TA(int i) :mi(i) {
cout << "TA()构造函数被执行" << endl;
}
TA(const TA &ta) :mi(ta.mi) {
cout << "TA()拷贝构造函数被执行" << endl;
}
~TA()
{
cout << "TA()析构函数被执行" << endl;
}
void operator()()//用类创建一个调用对象,不能带参数
{
cout << "mi1的值为!" << mi << endl;
cout << "mi2的值为!" << mi << endl;
cout << "mi3的值为!" << mi << endl;
cout << "mi4的值为!" << mi << endl;
cout << "mi5的值为!" << mi << endl;
cout << "mi6的值为!" << mi << endl;
}
};
int main()
{
int myi = 6;
TA ta(myi);
thread myclsobj(ta);//ta 可调用类对象
myclsobj.detach();// 等待子线程执行结束
cout << "\n主线程执行结束" << endl;
return 0;
}
所以,只要ta对象中不存在引用或是指针,就不会产生问题。所以用类对象构造多线程时最好不要用引用或是指针变量。并且最好用join。
2.2 lambda表达式
lambda表达式创建线程写法如下:
#include <iostream>
#include <thread>
using namespace std;
int main()
{
auto mylamthread = [] {
cout << "我的线程开始执行了!" << endl;
//...
cout << "我的线程结束执行了!" << endl;
};
thread mylmdobj(mylamthread);
mylmdobj.join();
cout << "\n主线程执行结束" << endl;
return 0;
}
用detach同样容易出现输出混乱问题
#include <iostream>
#include <thread>
using namespace std;
int main()
{
auto mylamthread = [] {
cout << "我的线程开始执行了!" << endl;
//...
cout << "1我的线程结束执行了!" << endl;
cout << "2我的线程结束执行了!" << endl;
cout << "3我的线程结束执行了!" << endl;
cout << "4我的线程结束执行了!" << endl;
cout << "5我的线程结束执行了!" << endl;
};
thread mylmdobj(mylamthread);
mylmdobj.detach();
cout << "\n主线程执行结束" << endl;
return 0;
}