搜索
首页php教程php手册以生活例子说明单线程与多线程

以生活例子说明单线程与多线程

1. 程序设计的目标

在我看来单从程序的角度来看,一个好的程序的目标应该是性能与用户体验的平衡。当然一个程序是否能够满足用户的需求暂且不谈,这是业务层面的问题,我们仅仅讨论程序本身。围绕两点来展开,性能与用户体验。

性能:高性能的程序应该可以等同于CPU的利用率,CPU的利用率越高(一直在工作,没有闲下来的时候),程序的性能越高。
体验:这里的体验不只是界面多么漂亮,功能多么顺手,这里的体验指程序的响应速度,响应速度越快,用户体验越好。

下面我们就这两点进行各种模型的讨论。

2. 单线程多任务无阻塞

以生活中食堂打饭的场景作为比喻,假设有这样的场景,小A,小B,小C 在窗口依次排队打饭。 假设窗口负责打饭的阿姨打一个菜需要耗时1秒。如果小A需要2个菜,小B需要3个菜,小C需要2个菜。如下:

阿姨(CPU):打一个菜需要1秒
小A:2个菜
小B:3个菜
小C:2个菜

那么在这种模型下将所有服务做完阿姨需要耗时 2 + 3 + 2 = 7秒
阿姨 = CPU
小A,小B,小C = 任务(这里是以任务为概念,表示需要做一些事情)
这种模型下CPU是满负荷不间断运转的,没有空闲,用户体验还不错。这种程序中每个任务的耗时都比较小,是非常理想的状态,一般情况下基本不太可能存在。

3. 单线程多任务IO阻塞

将上面的场景稍微做改动:
阿姨:打一个菜需要1秒
小A:2个菜,但是忘记带钱了,要找同学送过来,估计需要等5分钟可以送到(可以理解为磁盘IO)
小B:3个菜
小C:2个菜

这种情况下小A这里发生了阻塞,实际上小A这里耗费了5分钟也就是 300秒+ 2个菜的时间,也就是302秒,而CPU则空闲了300秒,实际上工作2秒。

所有服务做完花费 302 + 3 + 2 = 307秒  CPU实际工作7秒,等待300秒。 极大浪费了CPU的时钟周期。 用户体验很差,因为小A阻塞的时候,后面的所有人都等着,而实际上此时CPU空闲。所以单线程中不要有阻塞出现。

4. 单线程多任务异步IO

还是上面的模型,加入一个角色:值日生小哥,他负责事先询问每一个人是否带钱了,如果带钱了则允许打菜,否则把钱准备好了再说。

值日生小哥问小A准备好打菜了吗,小A说忘带钱了,值日生小哥说,你把钱准备好了再说,小A开始准备(需要300秒,从此刻开始记时)。
值日生小哥问小B准备好打菜了吗,小B说可以了,阿姨服务小B,耗时3秒
值日生小哥问小C准备好打菜了吗,小C说可以了,阿姨服务小C,耗时2秒
值日生小哥问小A准备好了没有,小A说还要等一会,阿姨由于没有人过来服务,处于空闲状态
300秒之后,小A准备好了,阿姨服务小A,耗时2秒

整个过程做完耗时 300 + 2 = 302秒  CPU工作7秒,空闲295秒

值日生小哥相当于select模型中的select功能,负责轮询任务是否可以工作,如果可以则直接工作,否则继续轮询。在小A阻塞的300秒里面,阿姨(CPU)没有傻等,而是在服务后面的人,也就是小B和小C,所以这里与模型3不同的是,这里有5秒CPU是工作的。 如果打饭的人越多,这种模型CPU的利用率越高,例如如果有小D,小E,小F…… 等需要服务,CPU可以在小A阻塞的300秒期间内继续服务其他人。实际上值日生小哥轮询也会耗时,这个耗时是很少的,几乎可以忽略不计,但是如果任务非常多,这个轮询还是会影响性能的,但是epoll模型已经不使用轮询的方式,相当于A,B,C会主动跟值日生小哥报告,说我准备好了,可以直接打菜了。

这种模式下用户体验好,CPU利用率高(任务越多利用率越高)

5. 单线程多任务,有耗时计算

回到最开始的模型,如下:
阿姨:打一个菜需要1秒
小A:200个菜
小B:3个菜
小C:2个菜

顺序做完所有任务,需要耗时 200 + 3 + 2 = 205秒, CPU无空闲,但是用户体验却不是很好,因为显然后面的 B,C 需要等待小A 200秒的时间,这种情况下是没有IO阻塞的,但是任务A本身太耗CPU了,所以说如果单线程中出现了耗时的操作,一定会影响体验(IO操作或者是耗时的计算都属于耗时的操作,都会导致阻塞,但是这两种导致阻塞的性质是不一样的)。在所有的单线程模型中都不允许出现阻塞的情况,如果出现,那么用户体验是极差的,例如在UI编程中(QT,C# Winform)是不允许在UI线程中做耗时的操作的,否则会导致UI界面无响应。 编写Nodejs程序的时候,我们所写的代码实际上是在一个线程中执行的,所以也不允许有阻塞的操作(当然整个Nodejs框架实现异步,一定不止一个线程)。

出现阻塞的情况一般有2种,一种是IO阻塞,例如典型的如磁盘操作,这种情况下的阻塞会导致CPU空闲等待(当然现代操作系统中如果IO阻塞,操作系统一定会将导致IO阻塞的线程挂起)。这种阻塞的情况,可以通过异步IO的方法避免,这样就避免程序中仅有的单线程被操作系统挂起。另一种情况下是确实有非常多的计算操作,例如一个复杂的加密算法,确实需要消耗非常多的CPU时间,这种情况下CPU并不是空闲的,反而是全负荷工作的。这种CPU密集的工作不适合放在单线程中,虽然CPU的利用率很高,但是用户体验并不是很好。这种情况下使用多线程反而会更好,例如如果3个任务,每个任务都在一个线程中,也就是有3个线程,A任务在ThreadA中,B任务在ThreadB中,C任务在ThreadC中,那么即使A任务的计算量比较大,B,C两个任务所在的线程也不必等待A任务完成之后再工作,他们也有机会得到调度,这是由操作系统来完成的。这样就不会因为某一个任务计算量大,而导致阻塞其他任务而影响体验了。

6. 多线程程序

我们将上面的模型改造成多线程的模型是怎样的呢,我们在模型5的基础上添加一个角色,管理员大叔(操作系统的角色):
阿姨:打一个菜需要1秒
小A:200个菜
小B:3个菜
小C:2个菜

加入管理员大叔之后变成这样的了,小A打两个菜之后,大叔说,你打的菜太多了,不能因为你要打200个菜,让后面的同学都没有机会打菜,你打两个菜之后等一会,让后面的同学也有机会。

大叔让小B打两个菜,然后让小C打两个菜(小C完成),然后再让小A打两个菜(完成之后小A总共就有4个菜了),再让小B打1个菜(此时小B总共打3个菜,完成),然后小A打剩下的196个菜。

CPU的利用率:很高,阿姨在不断的工作

用户体验:不错,即使小A要打200个菜,小B,小C也有机会。 当然如果小A说我是帮校长打菜,要快一点(线程优先级高),那也只能先把小A服务完

总耗时:   200 + 3 + 2 + (大叔指挥安排所消耗的时间,包括从小C切换回小A的时候,大叔要知道小A上次打的菜是哪两个,这次应该接着打什么菜,这相当于线程上下文切换的开销以及线程环境的保存与恢复),所以并不是线程越多越好,线程非常多的时候大叔估计会焦头烂额吧,要记住这么状态,切换来切换去也耗时间。

这种模型下实际上是将小A的耗时任务,分成多份去执行而不是集中执行,所以小A要完成他的任务,可能需要更多的时间(期间他也需要等别人,阿姨不会一直为他一个人服务,但是阿姨为他服务的时间是没有变化的),这种其实有点以时间换取用户体验(小B和小C的体验,小A的体验可能就不会那么好了,但是小A本来也非常耗时,所以多等一会是不是也没关系)

那么IO阻塞和CPU计算耗时阻塞这两者有什么区别呢? 区别在于IO阻塞是不使用CPU的,而CPU计算耗时导致的阻塞是会使用CPU的。 例如上面的例子中,小A说忘记带钱了需要同学送钱,于是小A等着同学送钱过来,这个过程中阿姨并没有为小A提供服务,这个过程中为小A提供服务的是他的同学(送钱过来),实际上小A的同学相当于现代计算机系统中的DMA(直接内存操作),小A同学送钱的过程相当于DMA从磁盘读取数据到内存的过程,这个过程基本不需要CPU干预。

当然在DMA技术还没有出现的年代,从磁盘读取文件也是需要CPU发送指令去读取的,也就是说需要CPU的计算,应用到这里的场景中,就是阿姨亲自跑一趟帮小A把钱拿过来。

7. 多CPU

多CPU是一个更加复杂的问题,多CPU如何调度? 小A在第一个窗口打两个菜,又跑到第二个窗口打两个菜这种情况如何处理。小A在第一个窗口,小B在第二个窗口他们要同一个菜,但是这个菜只够一个人,那么两个窗口阿姨如何分配这种需求(实际上应该是由操作系统也就是管理员大叔来决定如何分配,也就是多核下的线程同步与互斥)?

多核CPU情况下,多线程的调度,互斥,锁与同步相对来讲更加复杂,多核情况下是真正的并行,同一时刻有多个线程在同时运行,他们的竞争怎么处理,多个CPU之间如何同步(多CPU之间的缓存状态一致性)等等一系列的问题。

8. 多线程与多进程

上面描述的多线程实际上是讨论的是多线程的调度问题,这里我们说一说多线程与多进程与资源的分配问题。什么意思呢,一群人(多个线程)在一个桌子(进程)上吃饭,他们会涉及到一些问题,比如多个人可能会夹一个菜(竞争),A和B同时看到盘子里面有一块肉,同时伸出筷子去夹,A先夹走,B迟了一点伸到盘子的时候已经没了,只能缩回来(临界资源,互斥),有一个点心需要用馍夹肉一起吃。A夹了肉,B夹了馍,A需要B的馍,B需要A的肉,他们僵持不下谁都不让步(死锁)。

多线程之间的资源共享是非常方便的,因为他们共用进程的资源空间(在一个桌子上),但是需要注意一系列的问题,竞争,死锁,同步等。如果在旁边再开一个桌子(进程)。 那么桌子之间讲话,递东西又不方便(进程间通信),而开一个桌子的开销比在一个桌子上多加一个人的开销要大。另外一个桌子上的人数不可能无限制增加,桌子的容量有限也坐不下这么多人(进程的线程句柄是有限制的)。一个桌子坏了不会影响到另一个桌子上面人的就餐情况(进程间相互独立,一个进程崩溃不会影响另一个),而一个桌子上的某人喝挂了需要送医院,估计这一桌人都要散了(线程挂掉会导致整个进程也挂掉)。所以多线程与多进程是各有优缺点,不能一概而论。

说明:多线程桌子的比喻受到知乎用户[pansz]的启发,但是该比喻似乎说明不了线程同步的情况。

9. 总结

单线程程序:适合IO异步,不能阻塞,不能有大量耗CPU的计算。典型如Nodejs,还有一些网络程序

多线程程序:适合CPU密集型程序

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
聊聊Node.js中的多进程和多线程聊聊Node.js中的多进程和多线程Jul 25, 2022 pm 07:45 PM

大家都知道 Node.js 是单线程的,却不知它也提供了多进(线)程模块来加速处理一些特殊任务,本文便带领大家了解下 Node.js 的多进(线)程,希望对大家有所帮助!

优化Java文件写入多线程性能的方法有哪些优化Java文件写入多线程性能的方法有哪些Jul 01, 2023 am 10:05 AM

Java开发中如何优化文件写入多线程并发性能在大规模数据处理的场景中,文件的读写操作是不可避免的,而且在多线程并发的情况下,如何优化文件的写入性能变得尤为重要。本文将介绍一些在Java开发中优化文件写入多线程并发性能的方法。合理使用缓冲区在文件写入过程中,使用缓冲区可以大大提高写入性能。Java提供了多种缓冲区实现,如ByteBuffer、CharBuffe

Golang 的单线程特性及优势Golang 的单线程特性及优势Mar 18, 2024 am 11:51 AM

Golang的单线程特性及优势随着互联网和移动应用的蓬勃发展,对于高性能、高并发的编程语言需求日益增加。在这种背景下,Go语言(简称Golang)由Google公司开发并于2009年首次发布,迅速受到广大开发者的欢迎。Golang是一种使用静态类型、并发设计的开源编程语言,其最大的优点之一就是其单线程特性。Golang采用Goroutine的并发模型,

Python服务器编程:利用多线程解决并发问题Python服务器编程:利用多线程解决并发问题Jun 19, 2023 am 08:45 AM

随着互联网的发展,越来越多的应用程序被开发出来,它们需要处理并发请求。例如,Web服务器需要处理多个客户端请求。在处理并发请求时,服务器需要同时处理多个请求。这时候,Python中的多线程技术就可以派上用场了。本文将介绍如何使用Python多线程技术解决并发问题。首先,我们将了解什么是多线程。然后,我们将讨论使用多线程的优点和缺点。最后,我们将演示一个实例,

如何使用PHP多线程执行多个方法如何使用PHP多线程执行多个方法Mar 23, 2023 pm 02:11 PM

在PHP开发中,经常会遇到需要同时执行多个操作的情况。想要在一个进程中同时执行多个耗时操作,就需要使用PHP的多线程技术来实现。本文将介绍如何使用PHP多线程执行多个方法,提高程序的并发性能。

如何解决Java中遇到的代码性能优化问题如何解决Java中遇到的代码性能优化问题Jun 29, 2023 am 10:13 AM

如何解决Java中遇到的代码性能优化问题随着现代软件应用的复杂性和数据量的增加,对于代码性能的需求也变得越来越高。在Java开发中,我们经常会遇到一些性能瓶颈,如何解决这些问题成为了开发者们关注的焦点。本文将介绍一些常见的Java代码性能优化问题,并提供一些解决方案。一、避免过多的对象创建和销毁在Java中,对象的创建和销毁是需要耗费资源的。因此,当一个方法

Java错误:Java多线程数据共享错误,如何处理和避免Java错误:Java多线程数据共享错误,如何处理和避免Jun 25, 2023 am 11:16 AM

随着社会的发展和科技的进步,计算机程序已经渐渐成为我们生活中不可或缺的一部分。而Java作为一种流行的编程语言,以其可移植性、高效性和面向对象特性等而备受推崇。然而,Java程序开发过程中可能会出现一些错误,如Java多线程数据共享错误,这对于程序员们来说并不陌生。在Java程序中,多线程是非常常见的,开发者通常会使用多线程来优化程序的性能。多线程能够同时处

如何解决Java中遇到的并发编程问题如何解决Java中遇到的并发编程问题Jun 29, 2023 am 09:45 AM

如何解决Java中遇到的并发编程问题随着计算机技术的发展和应用场景的扩大,多线程编程在软件开发中变得越来越重要。而Java作为一种常用的编程语言,也提供了强大的支持来进行并发编程。然而,并发编程也带来了一些挑战,如数据竞争、死锁、活锁等问题。本文将探讨在Java中如何解决这些并发编程的问题。数据竞争数据竞争是指当多个线程同时访问和修改共享数据时,由于执行顺序

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前By尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版