简介📝

近来学习Rust的tokio库,发现许多知识还没理解透,特意在此梳理一下。

进程🔄

进程包括着N个线程,同时也包含N个线程共享的资源:内存资源、网络接口句柄、文件接口句柄等。一个进程至少会有一个线程。

线程🧵

线程是操作系统调度的基本单位。

线程是如何被调度执行的

假设如下:

  • 有一个CPU、且是单核心的
  • 所有线程的优先级都一样
  • 操作系统使用时间片调度算法
  • 目前有两个线程A、B
  • A、B都是同一个进程

A线程执行的伪代码:

for(int i=0;i<100;i++)
	printf("A");

B线程执行的伪代码:

for(int i=0;i<100;i++)
	printf("B");

假如当前正在执行线程A,这时候时间中断(8253A)到来,线程A被打断,跳转到操作系统的任务调度器,保存当前的上下文环境(例如保存寄存器值),然后获取到线程A的线程控制块(TCB),减少A线程的时间片,若不为0,说明当前线程的时间片还没使用完,恢复当前处理器的上下文环境,继续回去执行A。若为0,从任务队列里读取出B的TCB,从TCB中获取之前B被打断时的寄存器值到当前处理器,然后跳转过去执行。

协程

也叫用户级线程,其实就是在用户层实现一个调度器,操作系统对其是不感知的,在操作系统的视觉上看,跟普通的用户程序没区别。由于其开销比较小可以实现一些并发程序,使用比较广泛。

多线程下的开销

主要就是线程调度时的开销,包括

  • 不同进程之间切换线程
  • 相同进程之间切换线程

不同进程之间切换线程

属于不同进程之间切换线程,需要多做一步进程上下文的切换,例如虚拟内存空间的切换。同时还包括下面讲的线程之间切换的开销。

相同进程之间切换线程

既然同属于一个进程,那么共享资源的部分没必要变动了,所以节省了上面的一步。线程之间切换的开销:

  • 保存上下文、恢复上下文需要花费的时间
  • 调度器选择下一个待执行线程需要花费的时间
  • CPU缓存可能会失效,流水线可能要重新刷新

为什么多线程效率高

假如有任务A,任务B,执行时间都要1s。

CPU只有一个核心

任务A先执行,然后到任务B执行,共花费2s。

CPU两个核心

任务A和任务B同时执行,花费1s。

为什么有时候单线程效率比多线程高

对于同一个计算任务,有时候单线程完成的时间比多线程完成的时间还少。其实原因就是单线程的情况下去执行一个任务,在任务结束之前,每一次操作系统分配的时间片它一定都会使用完不会浪费,但是如果你把任务为为N个子任务,同时你分出N个线程去执行,你的线程在完成某个子任务时,可能时间片还没用完,但是子任务完成了,线程结束了,然后会返回到调度器执行,浪费了时间片,同时增加了额外的成本(跳到调度器执行),当线程数量足够多时,这个开销远远超过你使用多线程带来的收益。