2019 May 02 —— improvement; java

读浅谈多线程

多线程介绍

多线程的起源, 现在操作系统的智能支撑以及底层硬件的支撑,让程序运行更加高效.

并发 Concurrent

并行 Parallelism

并发是指的程序的设计结构,是逻辑上的概念,而并行则是指的程序的运行时状态,属于物理概念

并行是物理意义上的同时执行, 而并发是指能够让多任务在逻辑上交织执行的程序设计.

并发设计让并发执行成为可能,而并行是并发执行的一种模式.

构建并发程序能够更加高效的, 但同时也要求程序员更加理解计算机究竟做了什么?

现在操作系统能够同时运行多个程序, 这些特性支撑了你能够同时浏览网页和听音乐.每个程序独占一个系统进程,最终体现给用户就是所有程序同时运行.

进程并不是操作系统同时支撑操作的唯一手段, 在进程内部还可以内部构建子任务同时运行, 可以被认为是进程的一部分. 每个进程在启动时至少有一个线程, 启动时的线程被称之为主线程.随后可以随着程序员设计需要开启其他线程或终止其他线程.多线程编程是指在同一进程中运行多个线程,就像是你的音乐播放器有多个线程, 一个线程负责渲染 UI,一个负责播放音乐.

Operating System 可以被看做是一个容纳进程的盒子, 而进程又包含着一个或者多个线程.

进程线程

每个进程有自己由操作系统分配的独立内存空间,默认情况下, 内存空间不可共享. 浏览器无权限获取播放器的内存空间,进程之间共享数据的方式称为ipc.

与进程不同,线程之间共享由操作系统分配给其父进程的内存空间,线程之间的数据共享变得更加简单, 因而线程通常被认为更加轻量级, 消耗更少的资源创建, 更快创建, 也被称为轻量级进程.

线程是一种更加灵巧方便的手段让你可以在同时做多项操作的手段, 没有线程你需要使用多进程,然后使用操作系统的同步机制以及ipc机制来交互, 这通常更加复杂.

Green Thread

当前的多线程机制,如果创建新的线程需要通知操作系统进行调度,但并不是所有系统都原生支撑多线程,因而诞生了 GreenThread, 一种在系统层面不支持多线程条件下支撑多线程编程的模拟机制.

由于绕过了操作系统的底层机制,Green Thread 可以更加高效的创建和管理,GreenThreads 最早是 90 年代 Sun 设计推出支撑 Java的线程库.2000 Java切换到了 Native 线程的支撑.然而当前其他编程语言,Go/Rust/Haskell 等实现了一套类似 GreenThread 的机制.

多线程使用

并行工作在通常情况下能够提升效率,但并不总是这样:

  • 如果你的应用程序需要有序的做某些事情, 同时等待用户操作, 多线程可能没有那么有效
  • 在并行任务处理时,每个子任务都应该被小心设计.
  • 无法真正保证线程的确同时并行处理多项任务, 这依赖于操作系统.

如果操作系统没有支持在同时执行多项任务, 操作系统最终将不得不欺骗程序多任务并行执行, 感知起来是并行处理, 但实际上却是在每个任务轮换执行.

并发与并行的支撑.

CPU 运行程序的核心模块, CPU 核心, 这是计算真正处理的地方, 在设计中每个核心在一个时间点只能运行一个操作.

因而操作系统不得不开发了高级特性去给与用户能力去同时运行多任务, 即使是在单核心机器上. 这其中最核心的概念就是抢占式多任务处理机制.抢占式代表有能力中断一个任务切换到另一个任务执行,然后再能够在之后的某个时间回到之前的任务.

即使cpu只有一个核心,操作系统也能够在多项任务中, 以多任务切换的方式传递cpu核心的计算执行能力. 这就解释了在同一时间,多项任务的执行过程, 有了并发, 但真正意义上的并行实际上是没有的.

现代cpu由于多核机制的支撑使得真正意义上的并行成为了可能,操作系统可以通过检测cpu的核心数量, 分配任务到这些核心中执行,一个线程可能被操作系统分配到任意一个核心, 这种任务调度机制对于程序来说是完全透明的,通常系统会在多核间平均分配多项任务.

真正的并行执行多任务在单核上目前是不可能的. 然而如果应用能够受益于多线程, 还是有必要去编写了多线程任务执行的程序. 当进程使用多线程时,抢占式任务机制能够在某一项线程任务执行很慢或者 阻塞时执行其他任务.

如,如果 app 只有一个线程, 而这个线程当前在读取文件,在读取的过程中系统的 IO 性能是跟不上cpu性能的, 那么此时 cpu 等待 io 的读取,cpu 的资源就被浪费掉了.

问题

  • data race : 多线程之间读写数据, 当写线程还没执行完毕,读线程获取到了错误的数据.

  • race condition : 多项任务以不可预测的执行序列, 但实际我们期望的是多任务以特定的执行序列执行.

线程安全: 即使有多线程同时执行也没有 data race 和 race condition 出现.

data race 的核心原因在 atom, 原子性意味着 cpu 执行指令的最小不可切分独立单元, 原子操作天然具有线程安全性,当一个线程执行一个原子写入操作, 没有其他线程能够读取这份写了一半的数据.相反, 当一个线程在共享数据空间原子性的读取数据, 在一个单位执行时间读取了整个数据, 没有写入操作能够发生.因而, 没有 data race 能够出现的条件.

但事实上大部分操作都是非原子的,即使是 x=1, 在一个线程读取 x, 而另一个线程分配 x 的值的过程也能出现 data race.

race condition, 抢占式任务执行机制的执行依赖于操作系统,对于应用开发者的透明,意味着应用开发者无法控制多线程任务的执行顺序.对外表现就像是无法预知可判断, 同时调试起来也很复杂,无法通过有效控制重现过程.

并发控制

现实存在的并发问题如何解决?

  • Synchronization : 确保资源数据只能在同一个时间点下被一个线程使用,类似于标识部分代码为受保护的, 因而多线程不能同时执行这部分代码.

  • atomic operation: 得益于操作系统的支持下,一串非原子操作可能被转换成一个原子性操作.

  • immutable data : 剔除根本原因, 数据一旦分配无法更改, 只要数据不更改,所有线程都能安全的读取这份数据,这也是函数式编程的核心理念.


Quote:

gentle-introduction-multithreading

并发和并行?

上一篇
下一篇
Loading Disqus comments...
Table of Contents