学习前置:熟悉C++的基本语法,面向对象特性,对数据结构、计算机操作系统有相应的了解。清楚计算机中进程和线程的概念,清楚并发、同步、异步、互斥等术语.
尤其是要深刻理解
“宏观上并行,微观上串行”的意义。
“一核超频,七核围观”的现象体现了多线程编程的重要性。
计算机CPU中的8核8线程和8核16线程的区别和意义。
等内容详见高校操作系统教材。
一个进程必然包含一个主线程,进程和主线程密不可分。
thread:是一个标准库里的类
实例化一个thread对象即可操作线程
例:
void myfun()
{
...
}
int main()
{
...
std::thread myThreadObj(myfun);
myThreadObj.join();
...
return 0;
}
join():加入/汇合,作用是阻塞主线程(并非让主线程进入阻塞态),让主线程等待子线程执行完毕,然后子线程和主线程汇合。
注意:如果主线程执行完毕了,但子线程没有执行完毕,这种写法是不合理的,程序也是不稳定的。
一个书写良好的程序,应该是主线程,等待子线程执行完毕后,自己才能最终退出;
detach():分离,即主线程不再等待子线程执行完毕。
为什么引入detach()?:有开始当然要有结束。
注意:多线程程序应该从稳定的角度考虑,尽量让主线程等待所有子线程结束后再结束主线程。
一旦detach()之后,与这个主线程关联的thread的对象就会失去与主线程的关联,即子线程和主线程进入并发状态。
使用detach()之后,不能再join,否则会异常。
joinable():检测能否join,返回值为bool,线程detach或者join之后,就不能再join了。
我们先来看看一个简单的例子,来理解交替并行的线程。
为了体现出区别,多线程程序往往使用较为耗时的操作来测试和查漏。我们知道输入输出流是有系统IO调度的,是一个比较耗时的操作,可以用来测试。但实际问题中,有更多需求的时候还是考虑用计时器等精确延时来操作。
//main.cpp
#include <string>
#include <iostream>
using namespace std;
void Threadfunc()
{
cout << "我是子线程,我执行了1行" << endl;
cout << "我是子线程,我执行了2行" << endl;
cout << "我是子线程,我执行了3行" << endl;
cout << "我是子线程,我执行了4行" << endl;
cout << "我是子线程,我执行了5行" << endl;
cout << "我是子线程,我执行了6行" << endl;
cout << "我是子线程,我执行了7行" << endl;
cout << "我是子线程,我执行了8行" << endl;
cout << "我是子线程,我执行了9行" << endl;
cout << "我是子线程,我执行了10行" << endl;
cout << "我是子线程,我执行了11行" << endl;
cout << "子线程结束了" << endl;
}
int main()
{
thread my_tobj(Threadfunc);
my_tobj.join();
cout << "我是主线程 ,我执行了1行" << endl;
cout << "我是主线程 ,我执行了2行" << endl;
cout << "我是主线程 ,我执行了3行" << endl;
cout << "我是主线程 ,我执行了4行" << endl;
cout << "我是主线程 ,我执行了5行" << endl;
system("pause");
return 0;
}
可以看到,我在子线程创建之后就调用了join(),让主线程进入阻塞,等待子线程执行完毕。
结果如图:
如果我们把join注释掉,连续运行两次
场面可以说是混乱不堪,别说主线程和子线程之间,就连同一条语句中提行用的endl都没有正常的运行,主线程都已经结束了子线程仍在运行。多次结果都不一样。
学习了操作系统后可以知道,现代操作系统大多是在时间片轮转的基础上进行改进,综合多级反馈调节优先级的调度策略,这解释了为什么多线程程序的执行顺序不定,当然一般情况下不需要我们去担心系统调度的问题。对于操作系统来说,了解这个原因即可。
但对于多线程程序来说,这就体现了合理分配线程推进,和线程之间的同步互斥是非常重要的,这时候我们在书上学的PV操作信号量,消费者问题等就为我们提供了解决思路。
其他创建线程的方法
用类来创建:
//TA.h
class myThread
{
public:
void operatpr()() //不带参 重载()变成一个可调用对象
{
...
}
};
//main.cpp
int main()
{
myThread mt;
thread mytobj(mt);
//在初始化类的时候,不可以用匿名的对象
//如以下错误:
//thread mytobj(new myThread());
//这样是thread接受都是一个值而非我们想要的可调用对象
mytobj.join();
return 0;
}
这里引申出一个问题,调用了detach,当我的主线程执行结束了,那我实例化的mt这个对象还在吗?这里其实对象已经不在了,开始的时候就被复制到了线程里面,这就有一个重要的隐患,在对象中如果有来自其他线程的引用或者指针,这个值被线程以外的方法修改时就会引起程序的不安全甚至崩溃。
思考:detach之后不能再join是不是因为这个原因所以这么规定呢?
用lambda表达式来创建线程
auto mylamthread = []
{
dosomething();
....
}
thread mytobj(mylamthread);
mylamthread.join();
方法和用函数创建线程大同小异。