线程的概念
- 在程序里一个执行路线叫做线程,线程是“一个进程内部的控制序列”
- 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质时在进程地址空间运行
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
对于linux内核而言,没有线程的说法,内核对于创建线程而言就是创建一个轻量级进程(LWP)。
线程的说法是C库当中规定的,是由于操作线程的一系列接口都是库函数,而不是操作系统定义的接口。
线程是操作系统调度的最小单位,进程是操作系统分配资源的最小单位
线程优缺点
线程的优点:
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点:
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。 - 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 - 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。 - 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
多线程和多进程的区别
- 多进程:每一个进程都有自己独有的虚拟地址空间,这个也是进程独立性的原因,一个进程的崩溃不会导致另外一个进程受到影响。多进程的程序也可以提高运行效率(本质也是增加执行流),但是带来了进程间通信的问题
- 多线程:每一个进程当中的执行流共用同一个虚拟地址空间,所以一个执行流的异常会导致整个程序的退出。多线程的程序可以提高运行效率,但是会带来程序健壮性低,代码编写复杂(并发执行带来的后果)
创建线程
pthread_create:在内核当中创建一个PCB,PCB是指向父进程的虚拟地址空间的,内核会给创建出来的线程,在虚拟地址空间当中的共享内存开辟一段空间,保存线程独有的东西
进程–tgid 线程组id
线程id—pid
- 1.当当前程序只有一个执行流的时候,意味着当前的程序只有一个线程,我们将执行main函数的线程称为主线程(tgid=pid)
- 2.当程序有多个执行流的时候,意味着当前程序有多个线程
主线程:执行main pid=tgid
工作线程:新创建出来的执行流
tgid:相当于进程号(线程组id)是不会变的,标识当前的线程属于哪一个进程
pid:相当于线程id,每一个线程id是不同的
int pthread_create(pthread_t thread,const pthread_attr_t attr,void*(thread_start)(void),void* arg);**
- thread:线程标识符,和线程id不一样,pthread_t是线程独有空间的首地址,通过这个标识符可以对这个线程进行操作,调用pthread_create作为出参返回给调用者
- attr:线程属性:pthread_attr_t是一个结构体,这个结构体完成对新创建线程属性的设置,创建线程的时候,该参数被设置为null,则采用默认的属性。
- 线程的大小
- 线程的起始位置
- 线程的分离属性
- 线程的优先级调度属性
- thread_start:线程入口函数,接受一个函数的地址,这个函数是void返回值,void参数
- arg:给线程的入口函数传递的参数的值,不能传递临时变量,可以传递堆上开辟的内存
- 返回值:成功返回0,失败返回错误码
- void*传递任意的类型,包含自定的数据结构,类实例化指针对象
- 1.可以传递一个堆上开辟的内存到线程入口函数当中去使用
- 2.需要在线程入口函数结束的时候,将堆上开辟的内存释放掉
线程终止
线程终止的方式
- 1.从入口函数的return返回
- 2.pthread_exit(void* retval)谁调用谁退出
retval:当前线程的退出信息,也可以传null值
无返回值
当主线程调用pthread_exit退出的时候,进程是不会退出的,但是主线程的状态变成Z,工作线程还是R/S状态 - 3.pthread_cancel(pthread_t thread)
- 结束传入的线程标识符的线程,可以结束任意一个线程,只要知道线程的标识符就可以
- thread:线程标识符
- 返回值:成功返回0;失败返回错误码
pthread_t pthread_self():获取当前线程的标识符
线程在默认创建的时候,默认属性当中的线程是joinable的
joinable:当线程在退出的时候,需要其他线程来回收该线程的资源,如果没有线程回收,则共享区当中对于线程的空间还是保留的,退出线程的资源没有被释放,造成内存泄漏
线程等待
为什么需要线程等待
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
pthread_join(pthread_t void**)阻塞接口
- pthread_t:需要等待哪一个线程,传入要等待的线程标识符
- void**获取线程退出的时候
- return:获取入口函数return返回的内容,出参
- pthread_exit:获取pthread_exit(void*)的参数
- pthread_cancel获取到一个常数,PTHREAD_CANCELD
- 返回值:成功返回0,失败返回错误码
线程分离
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
线程分离意味着改变线程的joinable属性,变成detach,从而在线程退出的时候,不需要其他线程来回收该线程的资源(操作系统回收)
pthread_detach(prhread_t thread)
thread:该线程标识符指的是分离哪一个线程(设置哪一个线程的joinable属性为detach属性)
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
线程的独有和共享
- 独有:
tid:线程id、栈、信号屏蔽字、调度优先级、errno、一组寄存器 - 线程共享:
共享进程虚拟地址空间、文件描述符、当前进程的工作路径、用户ID和用户组ID