haribote&&linux0.11并发笔记整理抽取

1 初识并发

“在一段时间内,多个进程(线程)都被CPU执行”是并发初期更被广为流传的定义。

跟众多其他计算机技术一样,并发也得到了扩展。此文先从CPU进程特例层面认识并发。

p1 -=.  =.  =.  =.  =.  -.
p2    -.  =.  =.  =.  =.  =-
     | <--concurrency--> |

p1,p2: 进程1,进程2;
= : 进程持续被CPU执行时间;
. : CPU切换进程时间;
- : 进程启动或退出时间。

由于CPU速度快——切换进程时间很小(如0.1ms),若进程持续运行时间也被设置得比较小(如20ms),则p1和p2分别每20.1ms就会被持续执行20ms。

当进程数较少(如小于10)时,各进程间隔执行时间(小于201ms)和持续运行时间(小于200ms)在与人交互过程中不会被人感知出来,使得并发进程就像在同时运行一样。

是谁提出利用CPU速度支持程序并发的呢?幻想此文是他的组长吧——此文对他的欣赏可以从分配给他的年奖数看出些端倪。

2 进程并发执行的时间效率——并发执行 VS 串行执行

假设CPU没有运行其他进程。

2.1 并发 < 串行

比较当
[1] p1,p2不包含任何操作CPU外设的指令
[2] p1,p2不竞争或等待任何资源

/* The C-level routines of both p1&&p2 processes */
#incldue <stdint.h>

int main(void)
{
    uint64_t cnt = 0;
    while(++cnt) {
        ;
    }
    
    return 0;
}

时p1和p2并发和串行执行的时间效率。

p1 -=.  =.  =.  =.  =.  -.
p2    -.  =.  =.  =.  =.  =-
     | <--concurrency--> |

p1,p2并行执行时间
-=.-.=.=.=.=.=.=.=.=.-.=-
将CPU切换进程的时间放在末尾得
-=-========-=-...........

p1,p2串行执行时间
-=====--=====-

可以看出,p1p2并发执行比串行执行多消耗的时间来自进程切换。按之前为并发所假设的时间,进程切换时间只占进程持续运行时间的0.1/20=0.5%。

即若p1p2串行执行需1天时间,并发执行p1p2约多花7分钟。7分钟即0.5%是否会成为关键时间段,由具体应用对时间的苛刻程度决定。

在并发执行时间效率稍不如串行执行时,并发的优势在于,并发期间各进程都能被执行——即并发可较好地维持一碗水端平的现象。当然,并发期间会比串行占用更多的内存资源。

若对串并发的时间或计算机资源等差异不敏感时,就可在实现程序时略去对相应因素的考虑。

2.2 并发 > 串行

进程除了包含单纯的运算功能外,往往还包含与I/O交互的功能,或会竞争共享资源。在这些场景下,可通过并发提升CPU利用率从而从总体提升进程执行效率。

以进程与I/O交互为例看下并发为何能提升进程总体执行效率吧。

深谙CPU和I/O速度差距编程的娃不会将正等待I/O数据期间的进程加入并发行列中,而让CPU去执行其他可继续运行的进程,待I/O数据到来后再将相应进程加入到并发行中。

这种进程并发调度策略虽然不能提升(反而会些许降低)单个进程的执行效率,但能让CPU利用进程等待I/O的时间段去执行其他进程,从而为全局提升并发执行效率创造了可能性。

p3 -=.  =.  ^.    d =.  =.=.  -.
p4    -.  =.  =.=.=.  ^.  d =.  -
     |    <--concurrency-->    |

= : 进程持续被CPU执行时间;
. : CPU切换进程时间;
^ : 等进程所需资源时间(与CPU速度相比较长);
d : 进程等待资源来临时刻;
- : 进程启动或退出时间。

p3,p4并发执行时间为
=.-.=.=.^.=.=.=.=.^.=.=.=.-.-
              d       d

p3,p4串行时间为
==^^^^^^^^^^^^d===--====^^^^^^d=-

在进程等待I/O数据时间段,CPU能执行很多程序指令呢。以键盘输入为例,假设此文每分钟能键入1000个字符,即各字符平均间隔着60ms。60ms对于如今的计算机大概是多长一段时间呢——在i5-7300HQ 2.5GHz windows上,可以执行完一个计数约6万次的Python程序。

此文将windows和Python标黑加粗是为了突出在60ms期间,CPU远远不仅是做了6万次计数。

2.3 并发时间效率小结

[1] 与串行执行时间效率相比,单个进程在并发中的执行时间效率会下降。若各进程都是类似于p1p2这样全程可持续执行的进程,并发执行的总体时间效率也会下降。在时间效率不敏感时,就可以隐式地牺牲一些时间去换取更酷的“同时运行”效果。

[2] 若各进程因等待资源而间隔运行场景(闲暇时间)较多,在进程总切换时间小于进程总闲暇时间时,并发总体执行时间效率就能得到提升。或者是,在编写程序时,在能满足单个程序性能前提下应尽可能地降低CPU占用率,以提升无延迟感的并发量

[3] 在用并发追求时间效率时,进程切换(进程数量)和进程闲暇被权衡到位时,才会有效果。这种权衡力与三流程序员的拷贝力完全不等。不过,很多场景都不用去追求精准的时间效率——不卡就行。

3 h&&l并发调度

haribote&&linux0.11正是对CPU层面并发有一定见解才能写出适用于操作系统角色的并发调度程序。

3.1 haribote并发调度策略
|——————————————————————|
|         |----------| |
| level 9 |0 1 2...99| |
|         |----------| |
|         PCB          |
|——————————————————————|
            .
            .
            .
|——————————————————————|
|         |----------| |
| level 1 |0 1 2...99| |
|         |----------| |
|         PCB          |
|——————————————————————|

|——————————————————————|
|         |----------| |
| level 0 |0 1 2...99| |
|         |----------| |
|         PCB          |
|——————————————————————|
haribote分10个进程层级管理并发调度,
每个进程层级中最多同时包含100个PCB。

[1]
进程层级level数值越小其调度优先级越高;
处于同层级PCB所对应进程将被依次调度运行(定时器衡量运行时间)。

[2]
在进程层级调度标志置位或当前进程级已无可运行进程时,
调度程序会调度
包含可运行进程且优先级最高进程级中的进程运行。
level 9中置有闲置进程。

[3]
进程阻塞等待资源时,该进程将进入睡眠状态(PCB标识),
其PCB也将从进程级中移除,
然后根据[1]或[2]策略调度下一进程运行。

待该进程资源来临时,再恢复其可运行的状态,
并重新将其PCB加入到某进程级中。

若该进程需立即运行的话,
就可将其加入到进程级0中并置任务层调度标志。
3.2 linux0.11并发调度策略
|------------|
|0 1 2 3...63|
|------------|
PCBs

[1] 
从PCBs末尾往前遍历,唤醒——置进程状态为可运行状态
有信号且处于就绪状态的进程,而后遍历出
处于运行状态
运行时间片最大
的PCB,并切换该PCB对应的进程运行。

对于可运行状态进程的运行时间片相同时,
PCBs下标大者先运行。

[2]
PCBs[0]固定管理初始进程,初始进程运行时间片不会减少,
当有其他可运行进程时,初始进程不会被调度运行。

linux0.11用户态初始化完毕后,
初始进程运行时将唤醒有信号且处于就绪状态的进程,
然后按照[1]调度下一进程运行。

[3] 
用户进程运行时间片完毕(在定时器中自减),
按照[1]调度下一进程运行;

进程阻塞等待资源并进入未就绪状态时则按照[1]
调度下一个进程运行;待所等资源来临时,该进程被唤醒;

每次系统调用完毕后,
若当前进程运行时间片完毕或已成为未就绪状态 
则按照[1]调度下一进程运行。

linux0.11中进程调度管理涉及的进程状态。
          |调
       [1]|度
          V
      可运行状态
       ↗   ↖ 
     ↙       ↘
就绪状态 <--> 未就绪状态

僵死状态,停止状态。

4 认识其他并发

CPU更版的一些新功能会让进程切换时间增加——CPU与内存交换信息变得更多。

              |------------------|
              |       ...        |
              | |--------------| |
              | |instrs && data| |
              | |--------------| |
              | pr2              |
              |       ...        |
              | |--------------| |
              | |instrs && data| |<-----|
              | |--------------| |      |
              | pr1              |      |
              |       ...        |      |
              | |--------------| |      |
              | |   LDT items  | |      |
              | |--------------| |      |
              | LDT2             |      |
|-----------| |       ...        |      |
|   TLB     | | PCBs  ... PG_TABs|      |CS:EIP =
|    TR     | |       ...        |      |TSS1.cs:TSS1.eip;
|GDTR=n LDTR| | |--------------| |     ③|LDT1&&CS:EIP -->
|   CS  EIP | | |   LDT items  | |<-----|pr1当前指令地址;
|   others  | | |--------------| |      |
|-----------| | LDT1             |      |
CPU           |       ...        |      |
              | |--------------| |      |
              | |.ldt .cs .eip | |      |
              | |    others    | |      |
              | |--------------| |      |
              | TSS2             |      |
              |       ...        |      |
              | |--------------| |      |
              | |.ldt .cs .eip | |<--|  |
              | |    others    | |   |  |
              | |--------------| |   |  |LDTR =
              | TSS1             |   |  |TSS1.ldt << 64 + 
              |      ...         |   |  |GDT[TSS1.ldt >> 3];
              |n|--------------| |  ①| ②|LDTR --> LDT1;
              | |  GDT items   | |---|---
              | |--------------| |TR=q << 64 + GDTR[q >>3 ]
              | GDT              |--> TSS1;
              |       ...        |TLB=PAGE_TAB[x];
              -------------------|
              内存 */

大公司峰期并发需求量的增大不断促进并发技术的发展。

4.1 线程

线程是进程中的一段机器指令集,在程序中普遍以函数形式存在。一个进程中可包含多个线程,各线程共享进程内存地址空间。

与进程比较,线程可轻量化并发的上下文切换和内存资源占用。

               |---------------|
               |      ...      |
               | |-----------| |
               | |    ...    | |
               | | |-------| | |
               | | | stack | | |
               | | |-------| | |
               | | |instrs | | |
|------------| | | |-------| | |
|     TR     | | | thread_1  | |
| GDTR  LDTR | | |    ...    | |
|   CS  EIP  | | | |-------| | |
|   others   | | | | stack | | |
|------------| | | |-------| | |
CPU            | | |instrs | | |
               | | |-------| | |
               | | thread_x  | |
               | |    ...    | |
               | |-----------| |
               | process_m     |
               |               |
               |      ...      |
               |   GDT...LDT   |
               |     PCBs      |
               |     ....      |
               |---------------|
               memory 
可用进程并发的硬件和软件策略管理线程并发,
只是线程并发稍加轻量化:
上下文切换不涉及TLB切换;线程共享进程内存地址空间,
线程执行栈空间就是进程地址空间的一部分。
4.2 协程

协程是线程或进程中的一段机器指令即,在程序中普遍以函数形式存在——进程或线程中可包含多个协程。协程与线程相比,协程可进一步轻量化并发的上下文切换和内存资源占用。

协程上下文切换可仅在用户程序层面,可不在内核中分配数据结构体资源。进程和线程在机器指令层面发生上下文切换,协程在编程语言语句层面发生上下文切换,即在访问共享内存时,前者需要互斥访问机制(如锁),互斥可从不同程度地降低并发性。

|-----------------|
|   stack_space   |
|-----------------|
|      CTXs       |
|      ...        |
| coroutine_fn1 { |
|       .         |<-----|
|       .         |②     |
|    async I/O1   |---|  |
|       .         |   |  |
| }               |   |  |
|      ...        |   |  |
| coroutine_fn2 { |   |  |
|       .         |   |  |
|       .         |<--| ①|
|    async I/O2   |------|
|       .         |
| }               |
|   other instrs  |
|-----------------|
thread_x

如CPU执行到线程async I/O 2处无数据返回时,
便跳转coroutine_fn1处执行,待执行到
async I/O 1处且无数据返回时再跳转回
async I/O 2处......
这样可提升CPU利用率以从整体提升协程执行效率。

协程中上下文跳转信息保存在用户内存空间中,
比起进程和线程的上下文信息要轻量许多,一
般只包含寄存器信息。

除了类似基于栈的协程切换机制外,还有基于堆
实现的协程切换机制,如python中生成器(yield)。
4.3 I/O并发

进程、线程以及协程的精髓在于——在与慢速设备交互时,用尽可能小的切换代价去提升CPU利用率,以从总体上提升CPU所执行程序的效率。

这个并发思想也能用在I/O上,对于一个I/O设备,串行使用方式为

|---| 1 req  --> |--------------|
| C | 1 resp <-- |      I/O     |
| P |   ....     |    device    |
| U | n req  --> | e.g. network |
|---| n resp <-- |--------------|
即1个I/O请求,1个I/O响应依时进行。

较CPU请求速度比,I/O响应比较慢。所以可趁I/O响应期间继续发异步I/O请求(如向不同网站的请求),让各I/O各凭己速依次响应,以尽量塞满I/O工作量以提升I/O利用率——从总体上提升I/O请求效率。

|---| 1 req  --> |--------|
|   | 2 req  --> |        |
| C | 3 req  --> |  I/O   |
|   |   ...  --> |        |
| P | 3 resp <-- |        |
|   |   ...  --> |        |
|   | 1 resp <-- | device |
| U |   ...  --> |        |
|---| x resp <-- |--------|

在I/O响应期间继续发I/O请求,
让各I/O各凭己速响应,从而提升I/O利用率。

实际上,在用多进程、多线程以及协程与I/O交互时,已隐式应用了I/O并发。在对响应较缓慢I/O(如互联网)有大量请求时,可使用“协程+I/O并发”提升效率,尤其内存资源不够时——进程或线程并发较I/O并发会耗更多内存资源。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值