1.线程
1.1 概念
说线程之前,要先来说一下进程:进程是一个pcb,pcb就是操作系统对运行中程序一个描述,在linux下这个描述是一个task_struct结构体,这个结构体中描述了一个程序的所有运行信息,操作系统可以通过这个描述实现对一个程序的调度运行,因此进程是一个pcb。
线程:
1.线程是进程中的一条执行流,但是linux下实现进程中的执行流的时候,使用pcb实现。
2.因此就是linux下的线程是一个pcb,这些pcb在一个运行中的程序中共用同一个虚拟地址空间,相较于传统的pcb更加轻量化,称作轻量级进程。
3.因为同一个进程中的线程公用进程分配的资源,而进程是所有线程的统称,是一个线程组,系统在运行程序分配资源的时候是分配给线程组,分配给整个进程。
4.线程是cpu调度的基本单位,进程是资源分配的基本单位。
1.2 线程间的独有与共享:
独有数据:栈,寄存器,信号屏蔽字,errno
共享数据:虚拟地址空间,信号的处理方式,IO信息,工作路径,用户id/组id
1.3 线程的优点
1.创建一个新线程的代价要比创建一个新进程小得多。
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
3.线程占用的资源要比进程少很多 能充分利用多处理器的可并行数量。
4.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
5.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。6.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
1.4 线程的缺点
1.性能损失 2.健壮性降低
3.缺乏访问控制 4.编程难度提高
2.多线程
2.1 概念
多线程其实是实现多任务并发/并行处理的 | 多进程也是完成多任务的。
多线程的处理思路:一个运行中的程序中,具有多个执行流,各自完成一个功能模块实现。
2.2比较多线程与多进程
(1)多线程任务处理相较于多进程的优点:
1.线程间的通信更加灵活方便(除了进程间通信方式以外,还可以通过全局变量/函数传参实现通信)。
2.线程创建和销毁成本更低(线程间共享进程的的大部分资源,线程只是进程中一条执行流)。
3.同一个进程中的线程间调度成本更低(不需要重新加载切换页表、数据)。
4.线程执行粒度更加细致。
(2)多线程任务处理相较于多进程的缺点:
1.线程间缺乏访问控制,有些系统调用或者异常是针对整个进程产生效果。
2.多线程任务处理没有多进程任务处理稳定性高。
3.多进程应用场景:对主程序的稳定性安全性要求更高的场景,比如shell/网络服务器。
(3)多进程/多线程进行多任务的并行处理的共同优势:
1.IO密集型程序:程序中大部分工作都是进行IO 占用cpu极少
可以在一个执行流中发起IO,避免了以前只有一个执行流时,只有一个IO完成了之后才能进行下一个的情况,提高了IO效率。
在操作系统层面实现平衡化(循环调度:循环对一个线程中io发出请求,不需要等待上一个完成之后才能进行下一个的请求)并行压缩了IO的等待时间。
2.CPU密集型程序:程序中的大部分工作都是进行数据运算
CPU密集型程序中, 执行流的创建并不是越多越好,多了反而会提高cpu调度的成本
CPU密集型程序,线程的创建,最好是cpu核心数+1。
多执行流操作,可以更加充分的利用cpu资源,在多cpu情况下,多个执行流可以实现并行处理,提高效率。
3.线程创建
3.1 创建函数
函数:int pthread_creatre(pthread_t *tid,pthread_attr_t *attr,void* (*thread_routine)(void *arg),void *arg);
参数:tid:输出型参数,用于向用户返回线程id,每个进程在进程虚拟地址空间中都有一个相对独立的空间,返回的tid就是这个空间在虚拟地址空间中的首地址。
attr:用于设置线程属性,通常置NULL
thread_routine:线程入口函数
arg:最终会通过线程入口函数的参数传递给线程的数据
返回值:成功返回0失败返回非0值的一个错误编号
3.2 线程id
```cpp
pthread_t pthread_self(void);//获取当前调用线程的id
pthread_create()创建一个线程接口应用
pthread)self()获取线程id的接口应用
使用ps -L选项可以查看轻量级进程信息
LWP这一项是轻量级进程id,实际上就是pcb中的pid。
在外边查看到的进程id实际上是pcb中tgid–线程组id,线程组id等于进程中主线程的轻量级进程id的值。
3.3 tid如何成为线程的操作句柄
创建线程,会在进程的虚拟地址空间中开辟一块相对独立的空间作为线程的地址空间,这个空间包含用户态对线程的描述,实现对线程的操作,线程栈…;创建线程成功之后,会将这块空间的首地址返回给用户,用户对线程进行操作,就可以通过首地址找到线程的数据,进行一系列的操作,因此返回的线程地址空间的首地址就是线程的操作句柄,也就是pthread_create函数返回的tid。
线程地址空间也是在进程地址空间这内的一部分
tid:线程空间在虚拟地址空间中的首地址
PID:实际上是线程组id
LWP:实际上是每个线程pcb中的pid
4.线程终止
线程终止:退出一个线程
线程主动退出:
1.在线程入口函数中,调用return;(main函数中的return,退出的并不是主线程,而是进程)。
2.函数:void pthread_eixt(void* value_ptr);
参数 value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
主线程也可以退出,但是主线程退出,进程并不一定退出,所用线程退出,进程才会退出。
线程被动退出:
函数:int pthread_cancel(pthread_t tid);
功能:取消一个正在运行的线程
参数:tid:要取消的线程id
返回值:成功返回0;失败返回错误码
在主线程中调用pthread_exit可以退出主线程,但是不会退出进程,只有所有的线程都退出了,进程才会退出。
5.线程等待
线程等待:等待一个指定线程的退出,获取这个推出线程的返回值;并且允许系统回收这个线程占用资源。
但是:并不是所有线程都需要被等待,因为线程有一个属性,默认叫joinable,处于这个属性的线程,退出会不会自动回收资源,需要其他线程进行等待处理。
如何等待:
函数:int pthread_join(pthread_t tid,void** retval)
—(阻塞函数)
参数:tid:用于指定要等待的线程
retval:输出型参数,返回线程的退出返回值
返回值:成功返回0;失败返回错误码
6.线程分离
线程分离:设置线程属性,从joinable设置为detach,出去detach属性的线程退出后,会自动释放资源(这种线程不需要被等待)。
函数:int pthread_detach(pthread_t tid);
分离一个指定的线程–设置这个指定线程的属性为detach
分离一个线程,可以在任意位置,任意线程中完成