//F9加断点
一、并发、进程、线程的基本概念
1、并发:两个或者更多的任务(独立的活动)同时进行,一个程序同时执行多个独立的任务。以往计算机只是单核CPU,某一个时刻只能执行一个任务,由操作系统调度,每秒钟多次进行任务切换,不是真正的并发。现在计算机多是多核CPU,能够真正的并行执行多个任务(硬件并发)
2、可执行程序:磁盘上的文件,windows下扩展名为.exe的文件
3、进程:一个可执行程序运行起来就叫一个进程运行起来了,或者说进程就是运行起来了的可执行程序
4、线程:每个进程都有一个主线程(自动创建,一般是main函数),实际上运行程序的时候,实际上是该进程的主线程在运行,线程就是执行代码的一条道路,除了主线程之外,可以自己写代码创建其他线程,每创建一个新线程就可以多干一个不同的事,但是线程并不是越多越好(子线程最多不可超过300个),每个线程都需要一个独立的堆栈空间,线程之间的切换是要保存中间数据的,会耗费本该是程序运行的时间
总结线程:
a 线程是用来执行代码的
b 把线程理解为一个新的通路
c 一个进程自动包含一个主线程,主线程随着进程的自动启动和结束
d 多线程程序可以同时做多个事
二、并发的实现方法
a)多个进程实现并发
b)在单独的一个进程中,创建多个线程实现并发
1、多进程并发
world ie浏览器 爱奇艺等软件同时运行
进程之间的通信(同一台电脑)方法:管道、文件、消息队列等
进程之间的通信(不同电脑)方法:socket通信技术
2、多线程并发
每个线程都有自己独立的运行路径,且共享地址空间(共享内存)
全局变量、指针、引用都可以在不同线程之间传递,共享内存也胡存在一些问题,如数据一致性问题,例如线程1和线程2同时对一个变量进行操作时候,就会出现意外。
总结:线程如下优点
1)线程启动速度比进程快
2)系统资源开销更少
缺点:存在数据一致性问题
三、C++11新标准线程库(可以跨平台widows和Linux)
C++11增加了对多线程的支持,意味着可移植性
第二节
一、线程开始和结束
程序运行起来,生成一个进程,该进程所在的主进程自动运行
自己创建的线程也需要从一个函数开始执行,如果这个函数执行完毕该线程也运行结束
一般情况下,如果主线程执行完毕,子线程还没有结束,那么整个子线程会被系统强行终止
所以一般情况下,如果想保持子线程的运行状态,就要让主线程一直保持运行
1、thread是标准库中的一个类
2、join阻塞主线程,让主线程等待子线程执行完毕,
实例:
1 #include <iostream> 2 #include <thread> //尖括号表示系统头文件 3 4 using namespace std; 5 6 void myprint() 7 { 8 cout<<"我的线程开始执行"<<endl; 9 //可以做一些其他的事情 10 cout<<"我的线程执行结束"<<endl; 11 } 12 13 int main() 14 { 15 thread mytobj(myprint); //创建了线程,该线程执行起点为myprint(),该线程开始执行 16 17 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的 18 19 mytobj.join(); //主线程阻塞到这里,子线程继续执行,主线程等待子线程执行完毕,当子线程执行完毕,主线程就继续向下执行 20 21 22 cout<<"HelloWorld"<<endl; 23 24 return 0; 25 }
//如果不加join()此时这个代码有两条线同时在跑,打印"我的线程开始执行"和打印"HelloWorld"是通过不同线路来打印的
3、detach()
传统主线程要等待子线程执行完毕再推出,但是主线程也可以和子线程分离,即主线程可以提前结束,以提高程序运行效率
一旦detach()之后,与这个主线程关联的线程对象就会失去了与主线程join的资格
此时子线程在后台运行,这个子线程被C++运行时库接管
1 #include <iostream> 2 #include <thread> //尖括号表示系统头文件 3 4 using namespace std; 5 6 void myprint() 7 { 8 cout<<"我的线程开始执行"<<endl; 9 //可以做一些其他的事情 10 cout<<"我的线程执行结束1"<<endl; 11 cout<<"我的线程执行结束2"<<endl; 12 cout<<"我的线程执行结束3"<<endl; 13 cout<<"我的线程执行结束4"<<endl; 14 cout<<"我的线程执行结束5"<<endl; 15 cout<<"我的线程执行结束6"<<endl; 16 cout<<"我的线程执行结束7"<<endl; 17 cout<<"我的线程执行结束8"<<endl; 18 cout<<"我的线程执行结束9"<<endl; 19 cout<<"我的线程执行结束10"<<endl; 20 cout<<"我的线程执行结束11"<<endl; 21 } 22 23 int main() 24 { 25 thread mytobj(myprint); //创建了线程,该线程执行起点为myprint(),该线程开始执行 26 27 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的 28 29 mytobj.detach(); //主线程阻塞到这里,子线程继续执行,主线程等待子线程执行完毕,当子线程执行完毕,主线程就继续向下执行 30 31 32 cout<<"HelloWorld1"<<endl; 33 cout<<"HelloWorld2"<<endl; 34 cout<<"HelloWorld3"<<endl; 35 cout<<"HelloWorld4"<<endl; 36 cout<<"HelloWorld5"<<endl; 37 cout<<"HelloWorld6"<<endl; 38 cout<<"HelloWorld7"<<endl; 39 40 return 0; 41 }
打印可能是:
1 我的线程开始执行 2 我的线程执行结束1 3 我的线程执行结束2 4 HelloWorld1 5 HelloWorld2 6 HelloWorld3 7 HelloWorld4 8 我的线程执行结束8 9 我的线程执行结束9 10 我的线程执行结束3 11 HelloWorld5 12 HelloWorld6 13 HelloWorld7 14 执行完毕
//此时子线程和主线程同时执行,但是这样可能会存在主线内的代码执行完毕,但是子线程中的代码还没有执行完毕的现象
4、detachable():判断是否可以使用join或detach,返回true表示可以join,否则表示不可join
5、其他创建线程的方法
类对象也为可调用对象
1 例如: 2 #include <iostream> 3 #include <thread> //尖括号表示系统头文件 4 5 using namespace std; 6 7 class TA 8 { 9 public: 10 void operator()(); //不能带参数 11 { 12 cout<<"子线程开始1"<<endl; 13 cout<<"子线程开始2"<<endl; 14 cout<<"子线程开始3"<<endl; 15 cout<<"子线程开始4"<<endl; 16 } 17 18 }; 19 20 void myprint() 21 { 22 cout<<"我的线程开始执行"<<endl; 23 //可以做一些其他的事情 24 cout<<"我的线程执行结束1"<<endl; 25 cout<<"我的线程执行结束2"<<endl; 26 cout<<"我的线程执行结束3"<<endl; 27 cout<<"我的线程执行结束4"<<endl; 28 cout<<"我的线程执行结束5"<<endl; 29 cout<<"我的线程执行结束6"<<endl; 30 cout<<"我的线程执行结束7"<<endl; 31 cout<<"我的线程执行结束8"<<endl; 32 cout<<"我的线程执行结束9"<<endl; 33 cout<<"我的线程执行结束10"<<endl; 34 cout<<"我的线程执行结束11"<<endl; 35 } 36 37 int main() 38 { 39 TA ta; 40 thread mytobj3(ta); //ta为可调用对象 类对象也为可调用对象 41 mytobj3.join(); //等待子线程执行结束,也可以用detach() 42 43 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的 44 45 cout<<"HelloWorld1"<<endl; 46 47 return 0; 48 }
6、类中的私有数据含有引用或者是指针时候会出现意外
如下代码:
1 #include <iostream> 2 #include <thread> //尖括号表示系统头文件 3 4 using namespace std; 5 6 class TA 7 { 8 int & m_i; 9 public: 10 TA(int & i):m_i(i); 11 void operator()() //不能带参数 12 { 13 cout<<"m_i的值为"<<m_i<<endl; 14 cout<<"m_i的值为"<<m_i<<endl; 15 cout<<"m_i的值为"<<m_i<<endl; 16 cout<<"m_i的值为"<<m_i<<endl; 17 cout<<"m_i的值为"<<m_i<<endl; 18 cout<<"m_i的值为"<<m_i<<endl; 19 cout<<"m_i的值为"<<m_i<<endl; 20 21 } 22 23 }; 24 25 void myprint() 26 { 27 cout<<"我的线程开始执行"<<endl; 28 //可以做一些其他的事情 29 cout<<"我的线程执行结束1"<<endl; 30 cout<<"我的线程执行结束2"<<endl; 31 cout<<"我的线程执行结束3"<<endl; 32 cout<<"我的线程执行结束4"<<endl; 33 cout<<"我的线程执行结束5"<<endl; 34 cout<<"我的线程执行结束6"<<endl; 35 cout<<"我的线程执行结束7"<<endl; 36 cout<<"我的线程执行结束8"<<endl; 37 cout<<"我的线程执行结束9"<<endl; 38 cout<<"我的线程执行结束10"<<endl; 39 cout<<"我的线程执行结束11"<<endl; 40 } 41 42 int main() 43 { 44 int myi=6; 45 TA ta(myi); 46 thread mytobj3(ta); 47 mytobj3.detach(); 48 49 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的 50 51 cout<<"HelloWorld1"<<endl; 52 53 return 0; 54 }
主线程先执行完,子线程还在执行,myi是主线程的变量,主线程执行完myi就会被销毁,子线程再去使用myi的时候就会出错
或者将类TA中的公有数据改成不是引用也是可以的,这样就会使用主线程中myi的拷贝,这样就没有问题了
主线程结束后,ta也会被销毁,但是ta不在没有关系,ta是会被复制到了子线程中去的(所以在类中要有复制构造函数),所以不会因此出错,如下例程:
只要类中私有数据没有引用、指针,使用detach()就没有问题
1 #include <iostream> 2 #include <thread> //尖括号表示系统头文件 3 4 using namespace std; 5 6 class TA 7 { 8 private: 9 int m_i; 10 public: 11 TA(int & i) :m_i(i) 12 { 13 cout << "构造函数被执行" << endl; 14 } 15 TA(const TA & ta) :m_i(ta.m_i) 16 { 17 cout << "复制构造函数被执行" << endl; 18 } 19 ~TA() 20 { 21 cout << "析构函数被执行" << endl; 22 } 23 24 void operator()() //不能带参数 25 { 26 cout << "m_i1的值为" << m_i << endl; 27 cout << "m_i2的值为" << m_i << endl; 28 cout << "m_i3的值为" << m_i << endl; 29 cout << "m_i4的值为" << m_i << endl; 30 cout << "m_i5的值为" << m_i << endl; 31 cout << "m_i6的值为" << m_i << endl; 32 cout << "m_i7的值为" << m_i << endl; 33 } 34 35 }; 36 37 int main() 38 { 39 int myi = 6; 40 TA ta(myi); 41 thread mytobj3(ta); 42 mytobj3.join(); 43 44 //主线程可以做一些其他的事情,其中这些事情和myorint()函数是同时执行的 45 46 cout << "HelloWorld1" << endl; 47 48 system("pause"); 49 return 0; 50 }
执行结果:
但是主线程中的类对象ta执行析构函数没有显示不知道为啥。。注:以上方法也可以改为detach()
7、用Lamda表达式创建子线程(此处只写出了主函数)
1 int main() 2 { 3 auto mylambdathread = [] 4 { 5 cout<<"我的子线程开始执行"<<endl; 6 } 7 thread mytobj(mylambdathread); 8 mytobj.join(); 9 10 return 0; 11 }