并发实现机制-1-综述

并发是很多问题的基础,是对进程和线程管理的核心问题,并发是编程中相当重要的问题。但与此同时理解并发编程和理解面向对象的难度差不多,如果要理解他,则注定是一条艰辛的道路。

所有事物在任意时刻都只能执行一个步骤,即顺序编程是简单且基本的。但是对于某些复杂问题,并行的执行是方便的且必要的。

并发基本概念

操作系统核心问题是进程和线程的管理,不同情形有单处理系统、多处理器系统、分布式计算机系统等的进程处理。(todo: JAVA相关的描述放在此处) 并发问题涉及到的资源的问题不仅体现在内存、文件、I/O访问上,在对集合的处理和进程通信上也会涉及到并发。

并发提高了对处理器资源的利用率,理解并发有助于写出更好更快的程序。如果想要更好的进行网络编程、GUI编程、事件驱动编程函数式编程以及理解一些常见的软件架构(基于消息机制的架构)、分布式系统,都需要完整的了解并发问题,想更好的利用语言中的集合框架也需要理解并发问题。现在有专门的并发语言(例如Go,erlang这样的语言本身就支持并发)。此外并发也能帮助我们创建更为松散耦合的设计。

但是需要注意的是并发并不是完美和易用的(事实上是艰难的)。并发需要额外代价(包括复杂性代价、资源开销代价)来维护;并发环境下程序的封闭性被打破。并发数也不是越多越好,并发数需要合理并且CPU需要拥有足够的处理能力。如果没有任务会堵塞使用并发反而会引入上下文切换等一系列不必要的代价。

迷惑的问题

并发(Concurrency)和并行(Parallelism)

todo: 补充其他解释

并发是指某个时间段内,多任务交替处理的能力。每个进程执行一段时间后,记录工作状态,释放资源进入等待状态,让其他进程抢占CPU资源。

并行是指同时处理多任务的能力,CPU发展到了多核,可以同时执行多个互不依赖的指令和执行块。

并发和并行的核心区别为进程是否同时执行,他们都是为了尽可能快的执行外所有任务。”并行”概念是“并发”概念的一个子集。因此,凡是在求解单个问题时涉及多个执行流程的编程模式或者执行行为,都属于并发编程的范畴。

为了理解并发和并行给出以下参考:

参考1:某个科室两个专家同时坐诊,这是两个并行任务;其中一个医生时而问诊,时而查看化验单,然后继续问诊,突然又中断去处理病人的咨询,这是并发。

参考2:Erlang 之父 Joe Armstrong 用一张5岁小孩都能看懂的图解释了并发与并行的区别。在下图中并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机。

todo: 咖啡并行图

进程并发和线程并发

在某些系统中并发实体是线程而非进程(JAVA的并发实体是线程Thread)。使用线程的好处是代码不需要关注他是运行在一个还是多个cpu机器上,这样增加一个cpu就能很容易加快程序运行速度。

进程之间可以共享数据的方式大多需要复杂的实现(IPC), 而线程之间代码、地址空间是共享的,共享数据的方式更加高效。(进程要考虑隔离,一个进程没有办法直接访问另一个进程;线程不用隔离,线程之间共享内存)

todo: 信号量是在进程上的还是在线程上的??应该都有吧?进程并发和线程并发的实现不同??

并发的设计问题

  • 进程间的通信
  • 进程资源共享与竞争(内存、文件、I/O访问、处理器、缓冲区)(读写的可访问性)
  • 进程活动的同步
  • 进程处理器时间的分配

经典问题

todo: 补充其他问题示例

生产者/消费者问题、读者/写者问题。

并发原理

并发术语

  • 原子操作

    一个函数或动作由一个或多个指令的序列实现,对外部不可见。即其他进程不能看到中间状态也不能中断操作。指令序列要么全部执行要么都不执行。保证了并发进程的隔离。

  • 临界区

    进程访问共享资源的一段代码,如果一个进程正在临界区,那么其他进程不能在这段代码执行。

  • 竞争条件

    多个线程或进程在读写一个共享数据时,结果依赖于他们执行的相对时间的情形。发生在多个进程或线程读写数据时,竞争失败者决定竞争的变量的最终值。

  • 互斥

    一个进程在临界区访问共享资源,其他进程不能进入临界区访问任何共享资源的情形。

  • 死锁

    两个或两个以上的进程因为每个进程都在等待其他进程而不能继续执行的情形。

  • 活锁

    两个或两个以上的进程为响应其他进程的变化而持续改变自己状态但不做有用工作的情形。

  • 饥饿

    一个可运行的进程尽管能继续执行,但被调度程序无限期忽视,从而不被调度执行的情形。

并发原理

并发是进程(或线程)之间交互的问题,并发有很多情形,不同执行模式下的并发,不同进程交互程度下的并发,会有不同的表现,下面进行讨论。

交替和重叠的执行模式

单处理器多道程序系统中进程交替执行,多处理器中进程交替执行和重叠执行。其中进程的相对执行速度不可预测(执行速度取决于其他进程的活动、操作系统的中断处理方式、操作系统调度策略),引来了很多问题:

  1. 全局资源共享的危险(全局变量的不同的读写执行顺序)
  2. 操作系统很难对资源进行最优化分配
  3. 定位程序设计错误非常困难,调试困难
  4. 进程同时执行的情形(并行问题,只在多处理器出现)

考虑下面一段代码和分析举例:

todo: 单处理多处理器图解

在单处理器上,进程可在任何地方停止指令的执行,而多处理器还会两个进程同时执行并且都试图访问同一全局变量。解决这个问题就需要控制共享资源的访问,一次只允许一个进程进入。

进程的交互

todo: 课本上表格截图 表5.2

竞争的进程之间存在资源竞争: 当每个进程竞争使用同一资源时,会发生冲突,每个进程只访问资源(只读)不修改资源。这种情形需要访问互斥(读),通过对资源枷锁,提供锁机制可以来解决问题。

共享合作的进程,可能访问同一个共享变量、共享文件或数据库。进程可能访问或修改共享变量(读和写)而不涉及其他进程,但却知道其他进程也可能访问同一个数据。只需要保证修改互斥(写),但需要一个新的控制要求:数据一致性(todo: 画图举例)。在这种情形下临界区的概念非常重要,单一语句的互斥不再满足需求,

todo: 画图举例

通信合作的进程,由于未共享对象,因此不需要互斥,但仍然存在死锁和饥饿,两个进程都在接收对方通信的语句上堵塞,发生死锁(todo: 画图举例)。饥饿现象如图(todo: 画图举例

并发控制问题

控制问题

并发的实现带来了很多控制问题,其中三个主要的控制问题为:互斥、死锁、饥饿,另外还有其他的控制问题如数据一致性等。

todo: 此处控制问题图

互斥

并发核心问题是对资源的互斥需求,互斥的实现有多种方法(硬件方法和软件方法),互斥实现还需要注意忙等待等问题。

互斥技术可用于解决注入资源争用之类的冲突,也可以用于进程的同步。

互斥的要求

  • 强制实施互斥:在与共享资源或共享对象的临界区有关的所有进程中,一次只允许一个进程进入临界区
  • 在非临界区停止的进程不能干涉其他进程
  • 访问临界区的进程不能被无限延迟,即不会发生死锁或饥饿
  • 进程驻留在临界区的时间是有限的
  • 没有进程在临界区时,应当可以立即进入临界区
  • 相关进程的执行速度和处理器的数量没有任何要求和限制

互斥的方法:

  1. 专用机器指令,较少开销但不通用。(硬件支持)
  2. 操作系统或程序设计语言提供某种级别的支持。(软件方法:操作系统和程序设计语言的支持)
  3. 进程承当实现互斥的责任,这是软件方法,增加了开销并存在缺陷。(软件方法:进程承担责任)

其中方法1的几种实现有:中断禁用、专用机器指令;2的几种方法有:信号量、管程、消息传递等;方法3的几种实现有Dekker算法、Peterson算法、竞争条件和信号量等。这几种方法的详细说明将在下一篇文章总结。

不同系统的并发机制

UNIX 并发机制

  • 进程间传递数据
    • 管道
    • 消息
    • 共享内存
  • 触发其他进程的行为
    • 信号量
    • 信号

Linux 并发机制

  • 包含所有UNIX的机制
  • 实时信号机制
    • 支持优先级顺序排列信号
    • 多个信号同时排队
    • 可以随信号发送数值(整数或指针)
  • 原子操作
  • 自旋锁
  • 信号量
  • 屏障

Windows7 并发机制

  • 等待函数
  • 分派器对象
  • 临界区
  • 轻量级读写锁和条件变量
  • 所无关同步机制

Android 进程间通信

  • 连接器

参考

  1. 操作系统-精髓与设计原理(第八版)第五章
  2. 码出高效:Java开发手册 第七章
  3. 并发的艺术
  4. JAVA编程思想 第四版 第21章
posted @ 2020-03-23 00:32  cheaptalk肥皂  阅读(350)  评论(0编辑  收藏  举报