一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情。
第十六节开始我们正式进入了并发这一大章节
抽象线程
在CPU与内存的虚拟化中,我们探讨的硬件环境一直是一样的普通单核处理器,但是当代计算机的CPU通常会有多个核心,给予处理器同时处理多个过程的能力。相应的,一种抽象——线程被提出来了。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,线程之间有点类似于进程之间的关系,线程拥有独立program counter和register存储在thread control blocks(TCBs)中,通过context switch机制进行线程在单个processor上的转换,因此访问线程中的地址不需要通过PTL。
另外,线程在地址空间中的heap区以及code区是共享的,如右图所示。
引入线程这个抽象有明显的两大好处,一是并发处理,对一段数据线性的大批量处理可以交由多个线程分段共同完成,速度提升n倍,二是可以避免程序由IO引发的阻塞,当一个线程处理IO时,可以由另一个线程进行其它有意义的工作。
但是同时线程也带来了很多麻烦,由于线程的调度和进程基本上是一样的,所以假设只有一个核心,那么线程被创建之后的状态是不确定的,可能会立即执行,也可能会等待一段时间。这使得线程的执行顺序难以琢磨。由于共享内存,线程之间如果对一个公共变量同时进行操作,那么很可能会出现race condition(这里可以理解为某种冲突的情况)。
例如,对一个变量同时进行自增,结束时该变量结果并不是原来的两倍,并且它的结果是indeterminate的。这样的程序我们称之为critical section,对于这样的程序我们希望它是mutual exclusion的,即同一时间只有一个线程在进行操作。
造成这一情况的根本原因是未对这种新的抽象设计新的调度控制方法,因为普通的CPU指令所对应的操作是原子操作,例如上图中汇编指令mov、add。一个个的原子操作在按顺序执行时可能在关键的地方遇到time interrupt,尚未更改的数据又被另一个线程读取,造成数据紊乱。
为了解决这一问题,我们可以将当前的原子操作集合起来包装为synchronization primitives,让其也拥有原子操作“all or nothing”的特征。当然,要实现这一目标,我们要想出许多独创性的方法,必然需要硬件的支持和软件的构建。
当然,线程带来的问题不止这些,sleeping/waking interaction的实现也需要新的机制,这些都是后话了。
这一节是并发的开头,内容比较少,下一节来一波大的