一 进程

进程:就是二进制可执行文件在计算机内存中的运行实例,可以简单理解为:一个.exe文件是个类,进程就是该类new出来的实例。
进程是操作系统最小的资源分配单位(如虚拟内存资源),所有代码都是在进程中执行的。

在Unix系统中,操作系统启动后将会运行进程号(PID)为1的一个进程 init 进程,该进程是所有其他进程的父进程。操作系统通过 fork() 函数能够创建多个子进程,从而能够提升计算机资源的利用率。

进程在创建后会拥有自己的独立地址空间,操作系统会提供一个数据结构PCB来描述该进程(Process Control Block,进程控制块),PCB中保存了进程的管理、控制信息等数据。

PCB中保存了进程的管理、控制信息等数据,主要包含字段有:

进程ID(PID):进程的唯一标识符 ,是一个非负整数的顺序编号
父进程ID(PPID):当前进程的父进程ID
文件描述符表:即很多指向file接否提的指针
进程状态:就绪、运行、挂起、停止等状态
虚拟地址范围
访问权限 
当前工作目录
用户id和组id
会话和进程组

贴士:进程ID是可以重用的,当进程ID达到最大限额值时,内核会从头开始查找闲置的进程ID并使用最先找到的那一个作为新进程的ID

由于进程拥有互相独立的地址空间,所以进程之间无法直接通信,必须利用进程间通信(IPC,InterProcess Communication)方式来实现通信。

贴士:进程ID是可以重用的,当进程ID达到最大限额值时,内核会从头开始查找闲置的进程ID并使用最先找到的那一个作为新进程的ID。

二 线程

线程:操作系统基于进程开启的轻量级进程,是操作系统调度执行的最小单位(即cpu分配时间轮片的对象)

一个进程内部可以创建多个线程,他们与进程一样拥有独立的PCB,但是没有独立的地址空间,即线程之间共享了地址空间。这样也让线程之间无需IPC,直接就能通信!!(因为他们在同一个地址空间内)。

虽然线程带来了通信的便利,但是如果同一空间的中多个线程同时去修改同一个数据,就会造成资源竞争问题,这是计算机编程中最复杂的问题!

三 内核态与用户态

操作系统的内存会被划分为两大区域:

  • 内核区:提供了大量的系统调用函数,即最原生、最底层的操作函数,如 open(),write()
  • 用户区:加载、运行应用程序的区域,比如使用C语言写的程序,同样的C语言也提供了本语言的对应操作函数 fopen(),fwrite()。这些由编程语言提供的函数称之为库函数。

我们不难发现,库函数其实是在系统调用函数基础上再次进行了封装,方便开发者使用。当然开发者既可以使用库函数来操作文件,也可以直接使用底层的系统调用函数(但是这样需要做很多错误处理)。

程序在运行时,CPU有两种状态:

  • 用户态:当一个进程在执行用户自己的代码时处于用户运行态(用户态)
  • 内核态:当进程需要执行一些系统调用时,比如利用C的库函数fopen()时,fopen()虽然是库函数,但是执行时底层调用了系统的open()函数,此时程序进入内核态,调用结束后,程序会重新回到用户态!

操作系统之所以要这样设计是出于内存的安全考虑,内核地址只有内核自己的函数(系统调用函数)才能使用!

四 协程

进程和线程都是操作系统级别的,协程与他们并不是一个维度的概念,所以类似《现代操作系统》的书籍并未提出协程的概念。

贴士:千万不要将协程理解为轻量级线程!

协程:程序在执行时,函数内部可以中断,适当时候返回接着执行,即协程运行在用户态

协程的优势在于其轻量级、执行效率高:

  • 轻量级:没有线程开销,可以轻松创建上百万个协程而不会造成系统资源衰竭
  • 执行效率高:函数之间的切换不再是线程切换,由程序自身控制

线程需要上下文不停切换,而协程不会主动交出使用权,除非代码中主动要求切换,或者发生I/O,此时会切换到别的协程,这样能更好的解决并发问题。

五 并发

在早期的操作系统中,各个任务的执行完全是串行的,只有在一个任务运行完成之后,另一个任务才会被执行,我们称之为单道程序

而现代操作系统引入了多道程序的并发概念:

多道程序:当一个程序暂时不需要使用CPU的时候,系统会把该程序挂起或中断,此时其他程序可以使用CPU,多个任务在操作系统的控制中实现了宏观上的并发。
多道程序提升了计算机资源的利用率,但是也引起了多个任务对系统资源的抢夺,在开发上极为不便。

(一)串行与并发

串行与并发是同一个维度的概念,区别是:

  • 串行:指令按照顺序执行
  • 并发:指令并未按照顺序执行,而是在宏观上同时执行,即CPU不停的在各个任务之间来回切换,给人感觉所有任务同时执行了!比如电脑同时运行了QQ、浏览器,其实是CPU在这2个程序之间按照一定的调度算法在来回切换执行!

并行与并发并不是同一个维度上的概念:

  • 并行(parallel):在同一时刻(微秒级),多条指令在多个处理器上同时执行,并行一般要借助多核CPU实现!
  • 并发(concurrency):并未同时执行,只是由于CPU运行过快,给人产生同时运行的假象

并发与并行概念的区别是是否同时执行,比如吃饭时,电话来了,需要停止吃饭去接电话,接完电话继续吃饭,这是并发执行,但是吃饭时电话来了,边吃边接是并行。

(二)并发解决方案

  • 多进程:由系统内核管理并发,操作简单、进程互不影响。但是开销最大,占用资源较多,能开启的进程数极少,
  • 多线程:多线程在大部分系统上仍然属于系统层面的并发,开销较大,且会存在死锁管理问题。
  • 非阻塞I/O:基于回调的异步非阻塞I/O,尽可能少的运用线程
  • 协程:本质上仍然是用户态线程,但不需要系统进行抢占式调度,且真正的实现寄存于线程中,开销极小。

(三)各语言的并发理念

  • Java:典型的多线程并发模式,利用同步机制(加锁)来实现并发访问控制
  • Node.js:典型的单线程非阻塞I/O实践者,不存在Java的资源竞争问题,I/O操作处理完毕后才会利用事件机制通知业务线程返回结果,没有资源竞争的难题。
  • Go:典型的协程并发理念实践者,在语言本身层面实现了协程,协程之间通过管道进行数据传递

目前流行的并发理念是:异步非阻塞I/O,协程。