C++学习笔记-并发与多线程(1)
原创一、引入与线程
1、引入
我们知道,当一个可执行文件运行起来了,就产生了一个进程,而且进程里会含有一个主线程,这个时候主线程也会自动的开始运行,直到结束,主线程一结束,意味着这个进程也运行结束了。比如以下程序:
#include <iostream>
using namespace std;
void myPrint()
for (int i = 1; i <= 10; i++) {
cout << "我正在运行myprint()函数:-->" << i << endl;
cout << "我运行结束了" << endl;
return;
int main()
myPrint();
return 0;
}
像这样的主线程就是从main函数触发,中途调用myPrint(),然后回到main函数,结束运行;
结果如下:
运行示意图如下:
2、线程
从上面的示意图可以看出来,这是一条路走到黑的运行方式,单纯的只有这种方式肯定是不行的,所以我们要引入线程,开辟几条路出来并发执行。C++11是支持线程库的,只需要#include <thread>,加入线程后代码如下:
#include <iostream>
#include <thread>
using namespace std;
void myPrint()
for (int i = 1; i <= 10; i++) {
cout << "我正在运行myprint()函数:-->" << i << endl;
cout << "我运行结束了" << endl;
return;
int main()
thread myThread(myPrint);//创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
for (int i = 1; i <= 10; i++) {
cout << "我正在运行main()函数" << i << endl;
cout << "我运行完了主函数" << endl;
return 0;
}
这样的代码运行结果如下:
可是这样是有问题的,看运行结果,,发现主线程比myPrint()线程早执行完,,(对于一个进程来说,如果主线程结束,会把所有的子线程销毁,所以就会产生报错,,那怎么办?一个方法,我们可以让主线程睡上一会儿:运行结果如下:
即在头文件添加#include <Windows.h>,然后在创建线程下面加入Sleep(1000);
3、join()函数
这样一看输出结果是准确的,但是还是会异常,因为子线程始终是没有和主线程汇合,从而导致异常。那要怎么解决呢?
这里就要引入join函数:join意为汇合,子线程和主线程回合,使用这个函数,主线程会被阻塞在这里,等待子线程结束,然后与子线程汇合;代码如下:
#include <iostream>
#include <thread>
using namespace std;
void myPrint()
for (int i = 1; i <= 10; i++) {
cout << "我正在运行myprint()函数:-->" << i << endl;
cout << "我运行结束了" << endl;
return;
int main()
thread myThread(myPrint);//创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
for (int i = 1; i <= 10; i++) {
cout << "我正在运行main()函数" << i << endl;
cout << "我运行完了主函数" << endl;
myThread.join();
return 0;
}
运行结果如下:
4、detach()函数
主线程要在join()函数处阻塞,主线程越想越气,这样不公平,,确实,为了让主线程和子线程不汇合,而是各自结束,,那我们要引入:detach()函数,把上面代码的join改成detach()之后的运行结果
这里可以看到,主函数已经执行完了,而子线程基本上没有输出什么,,这是因为子线程被放到后台执行了,因此没有报错。
5、joinable()
joinable()判断是否可以成功使用join()或者detach()如果返回true,证明可以调用join()或者detach(),如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了。
#include <iostream>
#include <thread>
using namespace std;
void myPrint()
for (int i = 1; i <= 10; i++) {
cout << "我正在运行myprint()函数:-->" << i << endl;
cout << "我运行结束了" << endl;
return;
int main()
thread myThread(myPrint);//创建了线程,线程执行起点(入口)是myPrint;(2)执行线程
if (myThread.joinable())
cout << "可以调用可以调用join()或者detach()" << endl; //这里进入这个分支
cout << "不能调用可以调用join()或者detach()" << endl;
for (int i = 1; i <= 10; i++) {
cout << "我正在运行main()函数" << i << endl;
cout << "我运行完了主函数" << endl;
myThread.join();
if (myThread.joinable())
cout << "可以调用可以调用join()或者detach()" << endl;
cout << "不能调用可以调用join()或者detach()" << endl; //这里进入这个分支
return 0;
}
二、其他线程的启动方式
只要是可调用对象,基本上都可以当成启动方式:这里介绍仿函数,成员函数和lamda表达式
1、仿函数(重载());
代码如下:
#include <iostream>
#include <thread>
using namespace std;
class Ta
private:
int m_i;
public:
void operator()() //不能带参数
for (int i = 0; i <= 10; i++) {
cout << "传入的值"<< endl;
int main()
Ta ta;
thread myToj(ta);
myToj.join();
return 0;
}
运行结果如下:
如果把join()改成detach函数不应该会可能比子线程早执行完吗?如果比子线程早执行完,变量my和ta不应该被销毁了,,吗?
这是因为,类会执行拷贝构造,把变量复制一份到类里,证明如下:只需要写出来这几个函数
//Ta(int &i) :m_i(i) { cout << "传入的值" << i << endl; }
//Ta(const Ta& s) :m_i(s.m_i) { cout << "3333333333333333" << endl; }
//~Ta() {cout << "ee" << endl;}
可以看到,拷贝构造函数的和构造函数的都输出了,析构函数执行了两次,一次就是原先的,一次就是之后拷贝构造生成。
2、成员函数:
调用形式如下:
#include <iostream>
#include <thread>
using namespace std;
class Ta
private:
int m_i;
public:
Ta(int &i) :m_i(i) { cout << "传入的值" << i << endl; }
Ta(const Ta& s) :m_i(s.m_i) { cout << "3333333333333333" << endl; }
~Ta() {cout << "ee" << endl;}
void operator()() //不能带参数
for (int i = 0; i <= 10; i++) {
cout << "传入的值"<< endl;
void getMsg() {
cout << "get the message" << m_i << endl;
//调用形式改成:
int my = 6;
Ta ta(my);
thread myToj(&Ta::getMsg, ta);
myToj.join();
即可
3、lamda表达式
//调用形式为:
auto lamdaThread = []()->void {
cout << "我的线程开始运行" << endl;