目录
1.线程概念
- 1.1线程与进程的关系
- 线程是依附于进程才能存在的,如果没有进程,则线程不会单独存在
- 多线程其实是为了提高整个程序的运行效率
- 线程是执行流,执行用户写的代码
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是"一个进程内部的控制序列"
- 1.2曾经写的代码当中有没有线程呢?
- 理解以前的:
- 进程在内核当中就是一个task_struct,在该结构体当中的成员变量 pid 被我们称之为进程号。
- 现在要理解的:
- 1.linux操作系统当中没有线程的概念,程序员说的创建线程,本质上在1inux操作系统当中创建轻量级进程(1wp),所以轻量级进程等价于线程。
- 答案是:有的!曾经写的代码当中存在线程,就是执行mian函数的执行流,被称之为主线程,而程序员创建的线程被称之为叫做“工作线程”。
- 2. pid本质上是轻量级进程id,换句话说,就是线程ID
- 在task_struct当中
- pid_t pid; (轻量级进程id,也被称之为线程id),不同的线程拥有不同的pid
- pid_t tgid;(轻量级进程组id,也被称之为进程id)
- 一个进程当中的线程拥有相同的tgid
- 为什么进程概念的时候,说pid就是进程id?
- 因为主线程的pid和tgid相等
- 在task_struct当中
- 理解以前的:
- 1.3 linux内核创建线程
- 即在当前轻量级进程组中加了一个task_struct结构体
- 1.4 线程的共享与独有
- 共享
- 文件描述符表(线程A创建了一个文件,就会在文件描述表(fd_array数组)中添加一个新的元素,下标就是文件描述符,线程B也知道有这个文件)
- 用户id,
- 用户组id,
- 信号处理方式(信号的处理方式是操作系统内核定义的,因此每个线程的信号处理方式都是一样的)
- 当前进程的工作目录
- 独有:在进程虚拟地址空间的共享区当中
- 调用栈(线程调用函数时都会在自己的调用栈内开辟函数栈帧,就不会产生调用栈混乱的问题)
- 主线程用的是进程虚拟空间当中栈,而工作线程用的是共享区当中自己独有的调用栈
- 寄存器(线程退出的时候,要保存上下文信息)
- 线程ID,
- errno(线程执行自己的代码时,如果发生错误,就要设置自己的errno)
- 信号屏蔽字(每个线程都有自己的信号的阻塞位图)
- 调度优先级
- vfork()函数,创建子进程,且父子进程指向的内存虚拟地址空间是相同的,为了避免调用栈混乱的问题,vfork()函数规定子进程不退出的情况下,父进程不能执行自己的代码,即将阻塞父进程
- 1.5 线程的优缺点:
- 优点:
- 多线程的程序,拥有多个执行流,合理使用,可以提高程序的运行效率,多线程程序的线程切换比多进程程序快,付出的代价小(有些可以共享的数据(全局变量)就能在线程切换的时候,不进行切换
- 可以充分发挥多核CPU并行的优势
- 计算密集型的程序,可以进行拆分,让不同的线程执行计算不一样的事情
- I/0密集型的程序,可以进行拆分,让不同的线程执行不同的I/O操作,可以不用串型运行,提高程序运行效率
- 缺点:
- 编写代码的难度更加高(需要加锁,添加条件变量控制进程)
- 代码的(稳定性)鲁棒性要求更加高(保证资源分配的合理性)
- 线程数量并不是越多越好(CPU数量是固定的,线程数量过多会导致线程切换耗时大于正常业务耗时,反而降低了程序运行效率)(滑稽吃鸡)
- 程序的运行效率,是基于某个固定的机器配置,通过测试才能得出来
- 缺乏访问控制,可能会导致程序产生二义性结果
- 一个线程崩溃,会导致整个进程退出。(滑稽吃鸡)
- 优点:
- 共享
2.线程控制
- 2.1线程创建
- 2.1.1接口
- int pthread_create(pthread_t *thread, const pthread_ attr_t *attr , void *(*start_routine) (void*), void *arg);
- thread :出参,获取线程标识符(地址),本质上就是线程独有空间的首地址
- attr :线程的属性信息,一般填写NULL,采用默认的线程属性
- 属性信息当中比较关心的:
- 调用栈的大小(获取调用栈的大小是防止栈溢出的手段)
- 分离属性
- 调度策略:先来先服务,分时策略,时间片轮转,调度优先级等等
- start_routine :函数指针,线程执行的入口函数(线程执行起来的时候,从该函数开始运行,切记:不是从main函数开始运行)
- arg:给线程入口函数传递参数;
- 返回值:
- 成功,0
- 失败,<0
- 代码验证1:
- pstack [进程号] 命令
- 查看调用堆栈,从下往上看
- top -H -p [进程号] 命令
- -H:查看某个进程中线程的信息
- -p:指定进程号
- 代码验证2:
- 结论:线程把自己的入口函数执行完就会退出
- 代码验证3:创建多个线程
- 结论1:不要传递临时变量给线程的入口函数
- 结论2:如果给线程入口函数传递了一个从堆上开辟的空间,让线程自行释放
- 2.1.1接口
- 2.2线程终止
- void pthread_exit (void *retval );
- retval :线程退出时,传递给等待线程的退出信息。
- 作用:谁调用谁退出
- int pthread_cancel (pthread_t thread);
- 退出某个线程
- thread:被终止的线程的标识符
- pthread_t pthread_self(void);
- 谁调用获取谁的线程标识符
- 测试1:工作线程自己调用Pthread_cancel
- 测试2:让主线程退出,工作线程不退出
- 测试3:让主线程退出,工作线程执行一段时间后退出
- 测试4:让主线程退出,且主线程退出语句之后不再有循环体,工作线程执行一段时间后退出
- void pthread_exit (void *retval );
- 2.3线程等待
- 1.线程被创建出来的默认属性是joinable属性,退出的时候,依赖其他线程来回收资源(主要是退出线程使用到的共享区当中的空间)
- int pthread_join(pthread_t thread,void **retval);
- 是一个阻塞调用接口
- thread:线程的标识符(等待哪个线程就把哪个线程的线程标识符传递给函数)
- retval:退出线程的退出信息
- 第一种:线程入口函数代码执行完毕,线程退出的,就是入口函数的返回值
- 第二种:pthread_exit退出的,就是pthread_exit的参数
- 第三种:pthread_cancel退出的,就是一个宏:PTHREAD_CANCELED(在这种情况下,由于当前线程可能是其他线程终止的,当前线程不知道为啥退出,因此退出信息只能是一个宏)
- 代码验证:
- 2.4线程分离
- 设置线程的分离属性,一旦线程设置了分离属性,则线程退出的时候,不需要任何人回收资源。操作系统可以进行回收
- int pthread_detach(pthread_t thread) ;
- thread:设置线程分离的线程的标识符