文章目录
1. 概念
1.1 线程
- 线程:可以被看作是在某个进程中的一个控制流。
- 主线程:一个进程的第一个线程会随着它的启动而被创建,该线程被称为主线程
1.2 线程和进程
- 一个进程至少会包含多个线程(至少一个),线程必须属于一个进程。
- 进程中的所有线程都拥有自己的线程栈,并以此存储自己的私有数据。
- 进程中被线程共享的资源:
1.3 线程的标识
- 线程ID在系统范围内可以
不是
唯一的 - 在其所属进程的范围内必须
是
唯一的。
1.4 多线程和多进程
交换数据
- 多进程
通过 管道、消息队列、信号灯和共享内存区等手段完成
开发成本高 - 多线程
数据可共享
竞争
- 多线程
可能发生竞态条件而不得不使用一些同步工具(比如互斥量和条件变量)加以保护
会增加程序的复杂度,甚至可能造成死锁。 - 多进程
无
2. 线程控制
2.1 线程间控制
2.1.1 创建线程
创建过程:
- 调用线程创建新线程
任何线程都可以通过调用系统调用pthread_create来创建新的线程。我们把调用 系统调用 或函数 的线程简称为调用线程
- 在新线程中执行 start函数
调用线程需要给定新线程将要执行的函数以及传入该函数的参数值。由于代表该函数的参数被命名为start,因此我们通常称这个函数为start函数。
- 返回线程ID
如果新线程创建成功,调用线程会得到新线程的ID。
2.1.2 终止线程
调用系统调用pthread_cancel。其过程如下:
- 向目标线程发出一个请求,要求它立即终止执行。
- 目标线程接受线程取消请求,但等到时机相应该请求。
2.1.3 连接已终止的线程
过程
- 操作系统调用pthread_join 函数
- 将给定ID的线程 的start函数的返回值给调用线程
- 调用线程会接过流程控制权并继续执行pthread_join函数调用之后的代码
2.1.4分离线程
- 作用:
使一个线程不可被连接。
让操作系统内核在目标线程终止时自动进行清理和销毁工作。
2.2 线程自我控制
2.2.1 终止
有如下几种方式:
- 在线程执行的start函数中执行return语句会使该线程随着start函数的执行结束而终止。
- 显式地调用系统调用pthread_exit
2.1.2 分离
同样调用pthread_detach函数。
区别仅在于调用线程传递给该函数的线程ID是自己的ID。
3 线程状态
4 线程的调度
调度器的工作流程:
- 调度器将要使用CPU的线程放入
激活的优先级阵列
末尾 - 长时间占用CPU的线程 被
激活的优先级阵列
中的线程从CPU的运行队列中换下,放入过期优先级阵列
。 - 激活的优先级阵列中没有待运行的线程的时,调度器就会把这两个优先级阵列的身份互换。
- 被阻塞而进入睡眠状态的线程从运行队列中被移除
- 等待队列中的线程会随即进入睡眠状态。条件触发时会被内核唤醒,从等待队列移至相应的运行队列。
- 调度器会尽量使一个线程在一个特定的CPU上运行。
5 线程实现模型
5.1 用户级线程模型
该模型特点:
此模型下的线程是由用户级别的线程库全权管理的。
因此在调度器的眼里,进程是一个无法再被分割的调度单元。
优势:
资源消耗低
(模型特点导致的)缺陷:
- 导致在此模型下的多线程并不能够被真正地并发运行。
- 即使计算机上存在多个CPU,进程中的多个线程也无法被分配给不同的CPU运行。
5.2 内核级线程模型
该模型下的线程是由内核负责管理的。
优势:
可以真正实现线程的并发运行。
内核对线程的全权接管使操作系统在库级别几乎无需为线程管理做什么事情。
缺点:
内核线程的管理成本显然要比用户级线程高出很多。
5.3 两级线程模型
一个进程可以与多个KSE(内核调度实体)相关联。
首先,已被加载到进程的虚拟内存中的实现两级线程模型的线程库会通过操作系统内核创建多个内核级线程。
然后,它再通过这些内核级线程对应用程序线程进行调度。
大多数此类线程库都可以为实际运行的应用程序线程动态地分配若干个内核级线程。
优缺点:
虽然加大了管理工作的复杂度,但内存消耗大大降低,提高了线程管理操作的效能。
GO的协程
因为两级线程模型的实现的复杂性,它往往不被操作系统内核的开发者采纳。
但却可以很好地在编程语言层面上实现并发挥出其应有的作用。