进程-线程-协程简介

一、进程

进程是系统中资源分配和运行调度的单位,在对资源的共享和竞争中,进程间会相互制约。从结构上讲,每个进程都由程序、数据和一个进程控制块(Process Control Block, PCB)组成。

也可以理解为一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位。

  • 进程是操作系统对一个正在运行的程序的一种抽象结构。
  • 进程是指在操作系统中能独立运行并作为资源分配的基本单位,由一组机器指令、数据和堆栈等组成的能独立运行的活动实体。
  • 操作系统可以同时运行多个进程,多个进程直接可以并发执行和交换信息。
  • 进程在运行是需要一定的资源,如CPU、存储空间和I/O设备等。
1、进程的重要特征

进程最基本的特征是并发和共享特征

    1. 动态特征:进程对应于程序的运行,动态产生、消亡,在其生命周期中进程也是动态的、
    1. 并发特征:任何进程都可以同其他进程一起向前推进
    1. 独立特征:进程是相对完整的调度单位,可以获得CPU,参与并发执行
    1. 交往特征:一个进程在执行过程中可与其他进程产生直接或间接关系
    1. 异步特征:每个进程都以相对独立、不可预知的速度向前推进
    1. 结构特征:每个进程都有一个PCB作为他的数据结构
2、进程的状态与转换
  1. 进程的三种基本状态
  • a. 运行状态:获得CPU的进程处于此状态,对应的程序在CPU上运行着
  • b. 阻塞状态:为了等待某个外部事件的发生(如等待I/O操作的完成,等待另一个进程发来消息),暂时无法运行。也成为等待状态
  • c. 就绪状态:具备了一切运行需要的条件,由于其他进程占用CPU而暂时无法运行
  1. 进程状态转换
  • a. 运行状态 ===> 阻塞状态:例如正在运行的进程提出I/O请求,由运行状态转化为阻塞状态
  • b. 阻塞状态 ===> 就绪状态:例如I/O操作完成之后,由阻塞状态转化为就绪状态
  • c. 就绪状态 ===> 运行状态:例如就绪状态的进程被进程调度程序选中,分配到CPU中运行,由就绪状态转化为运行状态
  • d. 运行状态 ===> 就绪状态:处于运行状态的进程的时间片用完,不得不让出CPU,由运行状态转化为就绪状态
  1. 进程的类型
  • a. 系统进程:操作系统用来管理资源的进程,当系统进程处于运行态时,CPU处于管态,系统之间的关系由操作系统负责
  • b. 用户进程:操作系统可以独立执行的的用户程序段,当用户进程处于运行态时,CPU处于目态,用户进程之间的关系由用户负责

二、线程概念

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的
控制序列”。一切进程至少都有一个执行线程。

线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

  • 线程比进程更轻量
  • 线程能独立运行,独立调度,拥有资源(一般是CPU资源,程序计数器等)
  • 线程调度能大幅度减小调度的成本(相对于进程来说),线程的切换不会引起进程的切换
  • 线程的引入进一步提高了操作系统的并发性,线程能并发执行
  • 同一个进程的多个线程共享进程的资源(省去了资源调度现场保护的很多工作)

1、多线程中的三个核心概念

并发编程三个问题:原子性问题,可见性问题,有序性问题。

多线程同步是指多个线程访问锁定的代码段时,必须按照每个线程获取该代码段锁的顺序执行。

1.1、原子性

这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

1.2、可见性

可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。可见性问题是好多人忽略或者理解错误的一点。

CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。

这一点是操作系统或者说是硬件层面的机制,所以很多应用开发人员经常会忽略。

1.3、顺序性

顺序性指的是,程序执行的顺序按照代码的先后顺序执行。

2、线程安全

线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

只要线程之间没有共享资源,那么就是线程安全的,有共享资源,为了保证线程安全,需要引进锁的机制。

编写线程安全的代码,核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问。对象的状态是指存储在状态变量(实例或静态域)中的数据。对象的状态还可能包括其他依赖对象的域。(Map.Entry)

进程调度可以很好的控制资源分配,线程调度让进程内部不因某个操作阻塞而整体阻塞。协程则是在用户态来优化程序,让程序员以写同步代码的方式写出异步代码般的效率。

三、协程

关于协程,你可能看的最多的就是这样一句话“协程就是用户态的轻量级的线程”.

要理解是什么是“用户态的线程”,必然就要先理解什么是“内核态的线程”。 内核态的线程是由操作系统来进行调度的,在切换线程上下文时,要先保存上一个线程的上下文,然后执行下一个线程,当条件满足时,切换回上一个线程,并恢复上下文。 协程也是如此,只不过,用户态的线程不是由操作系统来调度的,而是由程序员来调度的,是在用户态的。

  • 协程是用户模式下的轻量级线程,操作系统内核对协程一无所知
  • 协程的调度完全有应用程序来控制,操作系统不管这部分的调度
  • 一个线程可以包含一个或多个协程
  • 协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下纹和栈保存起来,在切换回来时恢复先前保运的寄存上下文和栈
  • 协程能保留上一次调用时的状态,看到这里各种生成器(生成器是被阉割的协程)的概念浮现出来了。。
  • Windows下的实现叫纤程
3.1、实现一个用户态线程有两个必须要处理的问题:
  • 一是碰着阻塞式I\O会导致整个进程被挂起;
  • 二是由于缺乏时钟阻塞,进程需要自己拥有调度线程的能力。

如果一种实现使得每个线程需要自己通过调用某个方法,主动交出控制权。那么我们就称这种用户态线程是协作式的,即是协程。

3.2、为什么要用协程,协程有助于以下实现
  • 状态机:在一个子例程里实现状态机,这里状态由该过程当前的出口/入口点确定;这可以产生可读性更高的代码。
  • 角色模型:并行的角色模型,例如计算机游戏。每个角色有自己的过程(这又在逻辑上分离了代码),但他们自愿地向顺序执行各角色过程的中央调度器交出控制(这是合作式多任务的一种形式)。
  • 产生器:它有助于输入/输出和对数据结构的通用遍历。

四、程序,进程,线程这三者之间的关系?

进程是系统进行资源分配和调用的独立单位,每一个进程,都由它自己的内存空间和系统资源;线程是进程的执行单元,执行路径,线程也是程序使用CPU的最基本单位。

简单来说,一程序可以调用多个进程,比如一个视频播放器程序,里面就存在两个进程:一个是播放视频的进程,一个是下载上传视频的进程。

一个进程又同时调用多个线程,这个线程是隐藏的,用进程管理器看不到,可用其它的进程管理软件来查看。

三者的逻辑关系是程序调用进程,进程调用线程,一般来说程序下面都是多进程,不同的进程分工不同;进程下面也基本上是多线程的。

4.1、进程和线程的区别
  • 进程是资源竞争的基本单位
  • 线程是程序执行的最小单位
  • 线程共享进程数据,但也拥有⾃自⼰己的一部分数据。
4.2、线程和协程的区别

一旦创建完线程,你就无法决定他什么时候获得时间片,什么时候让出时间片了,你把它交给了内核。而协程编写者可以有一是可控的切换时机,二是很小的切换代价。从操作系统有没有调度权上看,协程就是因为不需要进行内核态的切换,所以会使用它。

三种调度的技术虽然有相似的地方,但并不冲突。进程调度可以很好的控制资源分配,线程调度让进程内部不因某个操作阻塞而整体阻塞。协程则是在用户态来优化程序,让程序员以写同步代码的方式写出异步代码般的效率。

五、PHP的TSRM

线程安全资源管理器(Thread Safe Resource Manager),这是个尝尝被忽视,并很少被人说起的“层”(layer), 她在PHP源码的/TSRM目录下。

PHP实现的线程安全主要是使用TSRM机制对 全局变量和静态变量进行了隔离,将全局变量和静态变量给每个线程都复制了一份,各线程使用的都是主线程的一个备份,从而避免了变量冲突,也就不会出现线程安全问题。

PHP对多线程的封装保证了线程安全,程序员不用考虑对全局变量加各种锁来避免读写冲突了,同时也减少了出错的机会,写出的代码更加安全。

但由此导致的是,子线程一旦开始运行,主线程便无法再对子线程运行细节进行调整了,线程一定程度上失去了线程之间通过全局变量进行消息传递的能力。

同时PHP开启线程安全选项后,使用TSRM机制分配和使用变量时也会有额外的损耗,所以在不需要多线程的PHP环境中,使用PHP的ZTS(非线程安全) 版本就好。

六、参考

1、进程,线程,协程
2、Java进阶(二)当我们说线程安全时,到底在说什么
3、线程,线程安全与python的GIL锁
4、并发基础知识 — 线程安全性
5、PHP的生成器、yield和协程
6、PHP中被忽略的性能优化利器:生成器
7、在PHP中使用协程实现多任务调度
8、多线程编程 - PHP 实现
9、PHP 进阶之路 - 揭开 PHP 线程安全的神秘面纱
10、进程详解(1)——可能是最深入浅出的进程学习笔记
11、操作系统 - 进程的概念
12、进程、线程、协程
13、进程 线程 协程 管程 纤程 概念对比理解

推荐阅读更多精彩内容