多线程入门学习
以下内容总结自KuangXiang老师免费分享在网易云课堂上的视频教程《C++11并发与多线程》,感谢KuangXiang老师的分享
1.基本概念
- 什么叫并发
两个或者多个任务(独立任务)同时发生;
单核CUP下是没有并发的,单核CPU下所谓的“并发”是由操作系统调度,每秒钟进行多次所谓的“任务切换”。这种任务切换称为上下文切换,上下文切换是有时间开销的,比如操作系统要保存切换时的各种状态信息、执行进度,待切换回来的时候要知道原来的状态。 - 什么叫进程
在windows下运行起来的一个exe文件就是一个进程。 因为主线程是自动启动,所以一个进程至少有一个线程即主线程。 - 什么叫线程
线程其实就是用来执行代码的。每个进程(即运行起来的可执行程序),都有一个主线程,这个主线程在当前进程中是唯一的,也就是一个进程中只能有一个主线程。当你执行可执行程序时会自动创建一个主线程。进程结束,主线程也就结束了。
运行程序时,实际上是进程的主线程来执行(调用)这个main函数中的代码。除了主线程外,也可以通过自己写代码创建其他线程,如果把线程理解成一个代码的执行通路或道路的话,自己创建的线程走的是别的道路。每创建一个新线程,就可以在同一个时刻,多干一个不同的事情(多走一条不同的代码执行路径)
线程不是越多越好,每个线程都需要一个独立的堆栈空间(1M大小),太多的线程占用内存,而且线程之间切换也是有时间消耗,线程太多,把时间都花费在线程切换上了,占用原本属于程序运行的时间。创建的线程最大数目不建议超过200-300个,有的时候线程太多反而会降低效率。- 主线程
主线程从main()开始执行,自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表这个线程运行结束。比如main函数运行结束,主线程就结束,而主线程结束代表整个进程执行完毕了,如果主线程结束了还有其他子线程在运行,那其他子线程也会被强制终止。如果想保持子线程的运行状态的话,不要让主线程结束(这句话也有例外)
- 主线程
//主线程开始执行如下main()函数
main(){
//..各种代码
return ;
//主线程执行完main中return后,表示整个进程运行完毕,此时主线程结束运行,这个进程也结束运行。
- 如何实现并发
手段:1.通过多个进程实现并发;2.在单独的进程中,通过创建多个线程来实现并发,自己写代码创建除了主线程外的其他线程。 - 多进程并发
word启动后是进程,excel启动后也是一个进程 同时启动两个程序就是多进程并发。
进程之间的通信:(1)同一台电脑:管道,文件,消息队列,共享内存;(2)不同电脑上:socket通信技术; - 多线程并发 (单个进程中创建多个线程)
每个线程都有自己独立的运行路径,但是一个进程中的所有线程共享内存空间(如全局变量,指针,引用 都可以在线程之间传递)。共享内存带来新的问题:数据一致性问题,比如多个线程对某块内存数据的操作。平时使用多线程多于多进程- 和进程相比,线程有如下优点:
(1)线程启动速度更快,更轻量级;(2)系统资源开销更小,执行速度更快,比如共享内存这种通信方式比任何其他的通信方式都快; - 缺点:
(1)多线程使用起来有一定难度,要小心处理数据的一致性问题。
- 和进程相比,线程有如下优点:
2. 写多线程程序
-
基本操作
-
需要包含
include<thread>
头文件,来使用thread
类 -
自己创建的线程也需要从一个函数开始运行(初始函数);
-
在
main()
函数中写代码
//p-1 //以下代码就有两个线程在跑,一个是主线程,一个是自己创建的线程,这里两个线程是同时进行,区别与函数调用 #include<iostream> #include<thread> using namespace std; void myprint() { cout<<"my thread~1"<<endl; cout<<"my thread~2"<<endl; cout<<"my thread~3"<<endl; cout<<"my thread~4"<<endl; } int main() { thread myt(myprint);//1.创建了线程,指定了线程执行入口是myprint()。2.该语句创建后,myprint函数就开始执行了 myt.join();//在这一步,join函数会阻塞主线程往下执行,让主线程停在这里等待子线程执行到这一步后,主线程往下执行下面的步骤, //myt.detach();//detach分离主线程和子线程,主线程不用等待子线程,两者可以分开跑,但是在主线程结束时,即到达`return 0` 时,子线程即使没有结束也会终止。 cout<<"In Main function"<<endl; return 0; }
-
-
thread
类
thread
类是标准库中的一个类,目的是用来创建线程的,p-1例子中myt
就是创建的一个thread
的对象。myt(myprint)
创建对象,并且用myprint()
函数初始化myt
对象。一旦初始化该对象后,myprint()
函数就开始执行,也就是该线程就开始执行了。
2.1.join()
函数
阻塞主线程,让主线程停在调用join()处,让主线程等待子线程执行到join函数处,主线程再开始接着执行。有些时候不使用join函数会出现主线程已经执行完,而子线程还没有执行完,程序出现异常终止。
2.2.detach()
函数 (使用的较少)
传统多线程程序,主线程要等待子线程执行完毕,然后自己再退出 。 ** 一旦调用了detach()函数,就不能在使用join函数,让主线程等待子线程了**
使用detach()
函数,主线程不用和子线程汇合了,主线程执行主线程的,子线程执行子线程的,主线程运行结束前子线程依然可以接着执行自己的剩余部分。 但要注意主线程结束时,子线程如果没有执行完也会被迫结束。- 引入detach的原因:
如果创建了很多子线程,让主线程逐个等待子线程结束,这样的编程方法不太好。
2.3.
joinable()
判断是否可以当前对象(比如myt
)是否可以使用join()
或者detach()
函数。如果可以使用,joinable的返回值为true
,即可以使用join或detach函数。 - 引入detach的原因:
-
创建线程的方法
创建线程,需要将可调用对象作为参数传递给thread
类对象。
3.1. 函数名作为可调用对象创建线程
比如例子p-1
就是将函数名作为可调用对象传递给myt
对象来创建了一个线程。
3.2. 类对象作为可调用对象创建线程
使用类对象时,要注意处理引用//p-2 #include<iostream> #include<thread> using namespace std; class TA { public: void operator()()//需要重载"()"类对象才能作为可调用对象 { cout<<"我的线程operator()开始执行了"<<endl; cout<<"我的线程operator()开始执行了"<<endl; } }; int main() { TA ta; thread myt(ta);//ta:可调用对象。 myt.join(); //myt.detach() cout<<"this in In Main function"<<endl; return 0; }
3.3. 用
lamda
表达式创建多线程//p-3 #include<iostream> #include<thread> using namespace std; int main() { auto mylamthread=[]{ cout<<"我的线程lamda开始执行"<<endl; //... cout<<"我的线程lamda执行结束"<<endl; }; thread myt(mylamthread); myt.join(); //myt.detach(); cout<< "this in In Main function1" << endl; cout << "this in In Main function2" << endl; cout << "this in In Main function3" << endl; return 0; }