目录
[1.1 什么是线程](#1.1 什么是线程)
[1.2 分页式存储管理](#1.2 分页式存储管理)
[1.3 线程的优点](#1.3 线程的优点)
[1.4 线程的缺点](#1.4 线程的缺点)
[3.1 POSIX线程库](#3.1 POSIX线程库)
[3.2 线程创建](#3.2 线程创建)
[3.3 线程退出](#3.3 线程退出)
[3.4 线程等待](#3.4 线程等待)
[3.5 线程分离](#3.5 线程分离)
1、Linux的线程概念
1.1 什么是线程
- 首先Linux内核不区分"进程"和"线程",统一用task_struct管理 。CPU处理的 是一个个task_struct。
- 进程 = 1个或多个task_struct (PCB) + 代码和数据
- 线程 (轻量级进程) = 1个task_struct (PCB) (它通过指针共享 了其所属进程 的代码和数据)
- 我们之前讲的传统的进程是一个主线程。
- 一个进程的多个task_struct(PCB),被认为是多个轻量级进程,主轻量级进程的id即lwp(light weight peocess)与进程的pid相同。
- 进程强调独占,部分共享;线程强调共享,部分独占。
1.2 分页式存储管理
- 磁盘I/O 和虚拟内存 (进程地址空间)的单位是"页 "(逻辑块(4KB))。OS,使用数组管理。
- 物理内存 的单位是"页框 "(物理块(4KB))。OS,使用数组管理。

- 一个进程不可能把所有内存4G(1024个页表 * 1024个页框 * 页框大小4KB)用完,所以页表的大小远小于4MB,所以一个进程只有一张页目录 + n张页表。
- 页目录里的元素称为页目录项,页表里的元素称为页表项。
- 页表项 里面存页框的起始地址 ,由于框的起始地址都是4KB的整数倍 ,所以地址的低12位 ,没有用上,就作为控制位 。高20位 ,正好完美覆盖 了32位CPU最大的4GB物理地址空间 (1024(10位) * 1024(10位) * 指向一个4KB)。 那页目录项 里面存页表的起始地址 呢?也是因为页表的起始地址都是4KB的整数倍?是的,地址的低12位 ,也作为控制位。
- 画外音:因此,整个页目录理论上最大可以管理 1024 * 4GB(对应1024个页表) = 4TB 的虚拟地址空间,但是CPU跟不上,哈哈。
1.3 线程的优点
都是由于共享所属进程 的虚拟内存空间和系统资源。
- 创建与销毁开销小。
- 切换效率高。共享地址空间,TLB(快表)与缓存有效。
- 资源占用少。
- 通信与数据共享便捷。
1.4 线程的缺点
- 性能损失 。多进程增加了额外的同步和管理开销。上下文切换和调度算法和竞争锁。
- 健壮性/稳定性降低。一个线程崩溃,会导致整个进程中的所有线程崩溃。
- 缺乏访问控制 。在一个进程中 ,所有线程 都共享相同的访问权限 。操作系统安全权限(如文件访问权限、用户ID等)的设置对象是进程,而不是线程。
- 编程与调试难度极高。
3、Linux的线程控制
3.1 POSIX线程库
- 因为用户区分线程 ,而Linux内核只认task_struct (共享代码和数据,就是线程之分,代码和数据独立,就是进程之分),需要线程相关的接口,所以pthread库 (POSIX线程库)封装 了内核创建共享资源任务的系统调用 ,并提供了一套标准、易用的线程管理接口。
- 在Linux中,C++11,就封装了pthread库。
- 每个线程 都有独立的栈空间 (调用不同的函数,创建独立的栈帧),主线程 用虚拟地址空间 里的栈 ,新线程 用动态库mmap出来 的栈 ;线程局部存储,使用**__thread**修饰(全局的或静态的)内置类型或部分指针,使数据独立。
- 在Linux中,pthread_setname_np() 和 pthread_getname_np() 是glibc提供的函数,它们通过系统调用(如 prctl())请求内核 ,将用户提供的字符串 写入或读出指定线程的内核结构体 struct task_struct 的 comm 字段。这是一个存储在内核空间的、全局可见的线程标识符。由于其修改和读取必须通过内核进行,由内核保证操作的原子性 ,因此不存在并发问题。
3.2 线程创建
- int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- pthread_t *thread:线程****id,输出型参数。pthread库彻底封装了轻量级进程,线程的id不是lwp,而是在库中的对应管理块的起始虚拟地址;pthread_t pthread_self(void);,获取当前线程的id。
- const pthread_attr_t *attr:设置线程属性,输入型参数,一般传nullptr。
- void *(*start_routine) (void *):函数指针 ,输入型参数。函数返回值为void*,函数参数类型为void*。线程以该函数为入口。
- void *arg:传给回调函数start_routine的参数。
- 返回值,On success , pthread_create() returns 0 ; on error , it returns an error number(正整数),
3.3 线程退出
- 从线程的入口函数return ,就是线程退出。推荐。exit,就变成了进程退出。
- void pthread_exit(void *retval);。线程退出。
- int pthread_cancel(pthread_t thread);。指定一个线程退出 。返回值,On success , pthread_cancel() returns 0 ; on error , it returns a nonzeroerror number(正整数).
- 线程的退出状态没有异常的概念,因为遇到异常,整个进程都退出了,由进程的退出状态判断。
3.4 线程等待
- 当一个线程结束 时,需要等待(即回收)该线程。不然会变成僵尸线程。
- int pthread_join(pthread_t thread, void **retval);
- pthread_t thread:线程id,输入型参数。
- void **retval:线程的退出状态 ,输出型参数。如果该线程被 pthread_cancel(自己cancel,也返回(void*)-1)了,进程的退出状态为**(void*)-1**(即宏PTHREAD_CANCELD)。
- 返回值,On success , pthread_join() returns 0 ; on error , it returns anerror number(正整数).
3.5 线程分离
- 进程默认是需要等待的(joinable),如果一个线程结束 时,不想等待(即回收)该线程,想让该线程自动回收,就要设置为分离状态(!joinable or detach)。
- int pthread_detach(pthread_t thread);
- 一般用于主线程分离新创建的线程,或新线程自己分离自己pthread_detach(pthread_self());。
- 画外音:主线程如果pthread_detach(pthread_self());,分离自己,当退出时,进程退出,系统试图去自动回收 一个正在执行进程退出流程 的线程 。这个线程的上下文正在被使用,却又要被清理。这就像一边拆房子一边还在房子里开会一样。最终,这通常会导致一个段错误(Segmentation Fault) 或其他形式的崩溃。主线程的生命周期与进程绑定 ,它的退出由进程退出流程自动管理,不需要也不应该手动设置其分离状态。
- 返回值,On success , pthread_detach() returns 0 ; on error , it returns an error number(正整数).