高并发与多线程可为java领域内的显学矣,某种程度上也可以算是软件领域的显学,大家都追求的东西。但是这其中也有些误会,比如很多人认为没有多线程,你的电脑就在听歌的时候不能打游戏,这是一种误解。我写的东西大多都成一个系统,本篇是多线程篇的总纲,并不依附于特定的语言,我记得线程也是放在操作系统中讨论的。

并发VS并行

我在刚学Java的时候,一边看视频,一遍看书,入门选的是官方写的教程《The Java™ Tutorials》,在讲到多线程之前的话是这样的:
Computer users take it for granted that their systems can do more than one thing at a time. They assume that they can continue to work in a word processor, while other applications download files, manage the print queue, and stream audio. Even a single application is often expected to do more than one thing at a time. For example, that streaming audio application must simultaneously read the digital audio off the network, decompress it, manage playback, and update its display. Even the word processor should always be ready to respond to keyboard and mouse events, no matter how busy it is reformatting text or updating the display. Software that can do such things is known as concurrent software.

计算机用户通常认为操作系统能够同时做很多事情是无比正常的事情,因为他们通常会在使用办公软件处理文字的时候,其他程序在下载文件,管理打印,处理音频。甚至是一个应用程序也是希望同时能够做不止一件事。例如,一个音频处理程序必须同时从网络上读取音频,然后解压缩,管理播放,更新进度(这是现在很稀松平常的事情,也就是在线听歌)。不管是文字处理软件有多忙,它也总是在时刻响应键盘和鼠标。能够同时做不止一件事情的软件,我们称之为并发软件。

上面的并发强调的是同时做不止一件事情,这是一种操作系统提供给程序的假象,事实上他们可能是交替执行的(并发),当然也可能是同时执行的(并行),这取决于当前计算机系统的基本配置和忙碌程度有关。假设当前计算机不是很忙碌,也就是说运行的程序并不多,又假设CPU很强大,进程的两个动作(现代操作系统来说一般是线程,现代操作系统调度的基本单位就是线程),就可能会被分配到这两个核心上同时执行。如果此时当前计算机系统相对来说初遇一种比较忙碌的状态,那么他们就只能排队执行,交替执行。

从不同的视角去看待问题,你会得到不同的答案。运行在操作系统上的任何进程,是无法独占CPU的,所以在一段时间内CPU会执行很多进程,这就是并发,即使说程序员做了一个不支持并发的软件(不能同时做不止一件事情),上面我们讨论的,一个音乐软件不支持在线播放,只能是缓冲完了再播放,那么我们只能说该程序从结构上来说是不支持并发的。从操作系统上来说,各个进程仍然是并发的,现代操作系统不会允许哪个进程完全独占CPU,进程们总是轮流使用CPU,在一段时间内重叠了,就是并发。比如进程A的起始时间是0,结束时间2,而进程B的起始时间是1,结束时间是4,那我们就可以说进程A和B是并发的,原因在于这两个的进程执行时间段存在交集。如果多个进程的执行时间段存在交集,那么我们可以称这些进程即为并发执行的。从操作系统的角度上来说,总是存在并发的,操作系统后台总是运行着很多进程,他们监控着操作系统的运行状态,执行时间存在交集。因此从全局上讨论并发意义并不是很大,我们需要单独的观察程序,也就是程序内部的并发,也就是程序本身在一段时间内可以做很多事情。

程序同时可以做不止一件事情,这样的软件,我们称之为并发软件,你很难见到非并发的软件。当我们说某个程序是并发程序的时候,通常说的是这个程序采用了并发的设计,也就是这个程序内的多个操作可以在重叠的时间段内进行,很经典的一个场景就是在线听歌,一个很简单的在线听歌,粗略的说可以拆成三个操作: 1. 向指定服务器发起请求获取音频下载到本地 2. 将下载的音频解压 3. 播放。

关于对并行的阐释《深入理解计算机系统》相当精妙: 如果两个进程并发的运行在不同的计算机或者CPU核心上,那我们就称这两个进程是并行的。我们来细致的分析一下这段话,并发的运行意味这两个进程的执行时间段存在交集,在这个交集上两个进程都在使用CPU,那么此时两个进程就是并行的。

对于操作系统来说,引入并发机制提升了资源的使用率,增强了操作系统的可用性。于计算机用户来说,他们就可以同时做不止一件事了,比如在处理word文档的时候,听听音乐,如今这都是司空见惯的事情。

但是故事到这里就结束了吗? 应该不是,万事万物都在不断发展,很快多进程的并发就碰到了一些问题,这也操作系统引入多线程的原因。我们需要明白,在一些涉及资源相关的操作的时候(比如I/O、线程、网络),我们总是调用操作系统的接口。

为什么要引入多线程?

我们尝试编写一个MP3播放软件,来体会一下多线程带来的好处,
核心功能模块有三个:
(1) 从MP3音频文件当中读取数据
(2) 对数据进行解压缩
(3) 把解压缩后的音频数据播放出来。
非并发版本,单进程的实现方法(为了不绑定在某一种语言上,这里我们用伪码来简单示意):

main(){
 while (true) {
            read(); // I/O 
            decompress(); // CPU
            play();
   }
}

上面的伪码,当然十分的简单,大意是不一次性读完,然后读一些,CPU解压一些,然后播放、
我们来大致的分析一下这个程序的弊端,我们知道I/O是很慢的,有的时候等半天,你才能听到一点点。
这样的软件体验是很差的,那么很自然我们就希望这个函数不必严格的互相等待,但是还是要有一点简单的主次关系,他们是并发执行,也就是说你肯定是先读,可以读的多一点,然后再解压、播放。这样的软件结构在没有多线程之前就是多进程方式,像下面这样
image.png

这样做优点是体验更好了,我们知道CPU是很快的,就像现在我的电脑上运行了很多进程,我依然在流畅的写博客。
所以我们可以先让读进程先跑一段时间,也就是多读一点数据,这样的话,这个MP3播放软件放音乐就更为让人满意。
但是问题又来了:

  • 多个进程如何共享数据,注意每个进程的地址空间都是私有的。
  • 除此之外,创建进程来说,开销是很大的,很多时候资源都是有限的。

共享数据来说,这个是有解决方案的,各位有兴致的话,可以去查查,但是还是不如在一个地址空间方便。 节约资源的话,
在进程身上寻求解决方案,是没有结果的,进程的开销无法避免,在进程身上找不到解决方案的话,我们不妨跳出去,也就是说提出一种新的实体,应当满足以下特性:

  • 实体之间可以并发地执行
  • 实体之间共享相同的地址空间,这样实体之间共享数据相对来说更为简单。

基于此,我们需要再度审视一下我们的进程:

  • 从资源组合的角度: 进程把一组相关的资源组合起来,构成了一个资源平台,包括地址空间(代码段、数据段)、打开的文件等各种资源,
  • 从运行的角度: 代码在这个资源平台上的一条执行流程。

粗略的说,我们可以将进程当做资源和执行流程的复合体,进程 = 资源 + 执行流程。执行流程往前再进一步就是线程,上面用进程展示的MP3播放软件,我们关注的也就是执行流程: 读取、解压、播放,我们可以将这三个执行流程放在一个进程里面,我们大致上可以这么讲,线程 = 进程 - 共享资源。显然我们可以看出多线程相对于多进程的优势,注意这里要强调一点,多线程相对于多进程来说并非是碾压级别的优势,比如chrome浏览器就是多进程,为什么chrome浏览器采取多进程机制呢,我们考虑一下,线程们是处在一个地址空间的,假设chrome是多线程,那么一个网页的某个线程因为某些原因发生了异常,那么势必影响到所有的网页,这是我们不想看到的。多线程是一种手段,多进程也是,要根据场景灵活的选择。多线程相对于多进程的优点就是:

  • 共享资源更为容易,因为都处于一个地址空间。
  • 各个线程之间可以并发的执行
  • 一个进程内可以有多个线程,更为节省资源。

缺点:

  • 一个线程的崩溃,可能会影响整个进程

进程 VS 线程

出现线程之后,进程的地位就又发生了变化。

  • 进程是资源分配单位,线程是CPU调度单位。
  • 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈。
  • 线程同样具备就绪、阻塞和执行三种基本状态,同样具有状态之间的状态关系。
  • 线程能减少并发执行的时间和空间开销:

    • 线程的创建时间比进程短,因为所需资源更少。
    • 线程的终止时间比进程短。
    • 同一进程内线程切换时间比进程短
    • 由于同一进程的各线程共享内存和文件资源,可直接进行不通过内核的通信。

当我们说起多线程与高并发时

当我们说起多线程与高并发时,我们就姑且理解为当前计算机上的资源使用率很高,场景在详细一些,比如购物节,用户访问量会比平时高很多,一般来说服务端的做法是,为一个用户开启一个线程来处理用户的请求,用户很多,然后就是多线程,很多线程必然是并发执行,所以说是高并发。

所以我个人认为当我们说起多线程与高并发时,说的就是在用户量访问量在一段时间猛增时,应该怎么去解决。

参考资料:
《操作系统》 向勇、陈渝。


北冥有只鱼
147 声望35 粉丝