多线程:概念、线程控制(创建、终止、等待、分离),线程安全(问题&实现),应用(生产者与消费者模型,线程池,单例模式)
(重要,因为多线程在实际工作使用较多,所以面试问的多)
1.概念
--线程是进程中的一个执行流程
线程是cpu进行执行调度的基本单元(调度一段代码的执行是通过线程完成的)
--进程是系统进行资源分配的基本单元
Linux下一个进程中是可以存在多个pcb的,一个pcb就是一个执行流程
一个进程中有多个pcb,和多个进程中有多个执行流程使用中有什么区别?
例子:零件加工厂,进行零件加工
多进程:如果有很多的零件加工,就相当于多开几个工厂,多个厂子可以同时加工零件
多线程:如果有很多的零件加工,就在一个 厂子里面,多开几条生产线。
哪个更好?
多进程---多建厂子:资源消耗大,更稳定,健壮
多线程--多开生产线:资源消耗小,健壮性不如多进程
线程到底是什么
线程是cpu调度执行的基本单元,而linux下pcb是程序运行过程的描述,因此linux下的线程通过pcb实现的,学了线程就知道一个进程有多个pcb的。(其他系统下进程有进程的描述,线程有线程的描述 )
因此有些人说linux下没有真正的线程,因为linux下的线程是一个pcb,被称为轻量级进程-LWP
多进程:占用资源多,但是健壮稳定
多线程:占用资源少,但是健壮性低,写代码需要小心
进程与线程的区别
进程是系统进行资源分配的基本单元(每运行一个程序,操作系统就要分配一次程序运行所需的资源)
线程是cpu进行执行调度的基本单元,在linux下是通过pcb实现的,一个进程中可以有多个pcb,被称作轻量级进程 LWP
多线程和多进程在多任务中处理中的优缺点:
多进程:健壮,稳定
多线程:
1.共享了虚拟地址空间,因此线程间通信更加灵活(包括进程间通信在内,还有全局变量,函数传参)
2.创建和销毁成本更低(线程之间很多资源都是共享的,创建一个线程不需要分配太多资源)
3.同进程的线程间调度成本更低(CPU上加载的块表信息,页表指针...不需要替换)
适用场景
对于程序的安全性要求大于性能和资源要求,使用多进程(比如shell);其他使用多线程
多个线程在同一进程中同时运行为什么不会混乱?
其实每个线程调度执行的就是一个函数
vfork--创建子进程,父子进程公用同一个虚拟地址空间,为了避免出现运行混乱,因此父进程阻塞直到子进程程序替换或者退出
多线程关于栈执行出现混乱的解决方案:把所有可能混乱的地方,给每个线程都单独整一份
线程之间的独有信息:标识符、栈、上下文数据、信号屏蔽字
给一个进程发送一个信号,进程中有多个线程(pcb),谁处理这个信号?--谁有时间谁就去(谁拿到时间片正在运行就是谁)
线程之间的共享信息:虚拟地址空间、文件描述符、信号处理方式、工作路径、用户ID、组ID
2.多任务处理
在多任务处理中,使用多执行流完成的优点:更加充分利用计算机资源,提高任务处理效率
那么在多任务处理中启动多少执行流比较合适呢?
执行流不是越多越好:因为执行流越多,cpu切换调度越频繁,执行流太多,返回会造成切换调度消耗大部分资源,启动多少执行流没什么固定数量,压力测试即可,不同程序对CPU要求不一样。
在任务处理中,程序分为两种程序:
cpu密集型程序:一段数据中几乎都是数据的运算(对CPU使用率高)
IO密集型程序:一段程序中大部分都是IO操作(大部分时间都是进行IO操作或者等待,对CPU使用率不高)
3.线程控制
主要为线程操作接口(创建、终止、等待、分离)
liunx下线程操作接口都是库函数,因为linux操作系统并没有直接向上层提供线程的系统掉用接口,直接基于系统的调用接口封装实现线程的相关接口。
线程库
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项
创建
int pthread_create(pthread_t *tid, pthread_attr_t *attr, void *(routine)(void*), void *arg);
tid:传入一个pthread_t类型变量的空间地址。用于接收线程ID--线程的操作句柄
attr:设置线程的属性,大部分属性都不用管;后面会有一个分离属性使用单独接口进行设置,attr为NULL表示使用默认属性
routine:是个函数指针,传入线程入口函数的地址,这个线程调度运行的就是这个函数
arg:给线程启动函数routine传入的参数
功能-----创建一个线程,指定这个线程要运行的函数routine,并给这个函数传入一个数据arg
返回值:成功返回0;失败返回错误码(非0)
注意:线程被创建出来后,谁先运行不一定 ,取决于操作系统的调度
线程只是一个执行流,说白了就是一个机器人,他做什么,什么时候 做,都是程序员来指定的
函数是一段功能的集合
线程是个执行流,线程是调度一个函数运行的
线程信息的查看
ps-L 选项进行查看(查看到其实是轻量级进程信息)
在多线程程序中,一个进程里面有多个pcb,当ps查看进程信息的时候应该显示谁的?
一个进程运行起来,默认会创建一个线程(pcb),这个线程有自己的pid
如果下边通过pthread_create创建一个线程(pcb),这个线程也有自己的pid,真正使用p查看进程信息的时候查看的是主线程pcb对应的信息
终止
线程终止:如何退出一个线程的运行
线程其实调度运行的是创建时所传入的入口函数,因此其实线程入口函数运行完了,线程就退出了
在线程入口函数中return;
注意:main中return退出的不仅是主线程,而是整个进程
在任意位置调用接口: void *pthread_exit(void *retval);
retval:用于设置线程的退出返回值
(上面两种方式都是主动退出,谁调用,谁退出)
在任意位置调用接口: int pthread_cancel(pthread_t tid);
注意:这个接口用来取消指定线程运行的,给谁tid,推出谁
一个线程如果是被取消的,则他的返回值就不是一个正经的返回值 了
等待
主线程退出,其实不影响其他线程的运行(不多见)
所有的线程退出了,则进程退出释放所有资源;(所有生产线停了,厂子没必要存在)
进程退出了,会先退出所有的线程;(一个厂子塌了,所有生产线也都坏了)
其实一个线程退出了,资源也并没有完全释放(因为要保存返回值)
僵尸进程:子进程退出了,为了获取退出码,因此没有直接释放资源,等待父进程处理
等待:等待指定的线程退出,获取退出线程的返回值,回收退出线程的所有资源
线程之间传递数据要尤其注意数据的生命周期
如果一个线程是被取消的,则获取的返回值是PTHREAD_CANCELED 本质是个 (void*)-1
分离
在线程属性中,有一个分离属性,默认值是joinable,表示线程退出后,不会自动释放资源,需要被其他线程等待。
但是我们并不关心一个线程的返回值,也不想等待他的退出,则这时候将这个分离属性设置为detach状态;
detach状态,表示线程退出后,自动释放所有资源,不需要被等待(资源是自动释放的,因此也不能被等待--等待会出错)
接口: int pthread_detach(pthread_t tid);//设置指定线程的分离属性为detach