一.线程
1.基本概念
线程是比进程粒度更细的执行流,在一个程序里的一个执行路线叫做线程。一个进程至少有一个执行线程
理解:
- 每一个线程的数据代码都是目标进程的子集
- 线程于线程数据和代码大部分都是共享的
- 线程的创建和释放成本非常低
- 线程的调度成本很低
线程是在进程内部的执行流,Linux下没有真正的线程,是进程模拟线程(操作系统没有专门为线程创建对应的结构体),Linux下的线程叫做轻量及进程
注意:
- 进程内部至少有一个线程
- 线程在进程的地址空间运行
- 进程是分配系统资源的基本单位
- 线程是CPU调度的基本单位
- 线程对大部分资源是共享的
- 线程有自己私有独立的上下文数据
- 线程具有私有的栈结构
- 线程必须被等待,否则会造成僵尸进程似的内存泄漏问题
2.线程私有的和多个线程共享的数据
- 每个线程私有的数据:线程ID,一组寄存器(保存自己独立的上下文信息),栈(私有栈),信号屏蔽字,调度优先级
- 进程内部多个线程共享的数据:文件描述符,每种信号的处理方式
3.线程的优点
- 创建一个新线程的代价要比创建一个进程小很多
- 与进程的切换相比,线程之间的切换需要操作系统做的事情很少
- 线程占用的资源比进程要小很多
- 能充分利用多处理器的可并行数量
4.线程的缺点
- 性能损失
- 健壮性降低
- 缺乏访问控制
- 编程难度提高
二.线程控制
1.POSIX线程库(用户级线程库)
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“.pthread_”打头的
- 要使用这些函数库,要引入头文件
- 链接这些线程函数库时要使用编译器命令的“-lpthread‘选项
2.创建线程
- int pthread_create(pthread_t thread, const pthread_attr_t *attr , void *(*start_rountine)(void *), void *arg)
- 第一个参数表示为线程ID,第二个参数设置为线程属性(NULL表示默认属性),第三个参数是函数地址,线程启动后要执行的函数,最后一个参数是传给线程启动函数的参数
- 返回值:成功0;失败返回错误码
3.线程等待(主线程等待)
- int pthread_join(pthread_t thread , void **value_ptr);
- 阻塞式等待,第一个参数为索等待的线程ID,第二个参数为输出型参数,获得所等待线程的退出信息
注:已经退出的线程,其空间没有释放,仍然在进程的地址空间内,创建新的线程不会复用刚才退出线程的地址空间,所以需要等待,线程退出不可能是异常退出,因为异常退出的话整个进程就挂了,没有机会等待了
4.线程终止
三种终止情况;
- 代码跑完,结果正确
- 代码跑完,结果不正确
- 异常退出,在运行期间,只要有一个线程出错异常退出,全部线程(进程)就会挂掉
只终止某个线程而不整个进程的方法:
- 从线程函数return。这种方法对主线程不适用(进程退出)
- 线程可以调用pthread_exit终止自己
- 一个线程可以调用pthread_cancel终止同一进程中的另一个线程
5.线程ID及地址空间布局
多线程的进程,有被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应,进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID,进程描述符中的tgid,该值对应的是用户层面的进程ID
这里的pid就是getpid的返回值,LWP就是gettid()的返回值
ptbread_t pthread_self(void)//获得线程ID(无符号长整形)%lu
进程地址空间布局
6.线程分离
- 默认情况下新创建的线程是joinable(可结合的),线程退出后,需要对其进行等待,否则无法释放资源,从而造成系统泄漏
- 如果不关心线程的返回值,join就是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源
- int pthread_detach(pthread_t thread);//线程组内的其他线程对目标线程进行分离,也是线程自己分离
- 动态链接库在加载到内存时,是可以被多个进程映射到共享地址空间的,线程一旦被分离,就不需要被主线程释放,该线程一旦(堆栈之间的共享区)停止。系统会自动释放资源