并发的本质:线程与进程区别
关注我,可了解更多有趣的面试相关问题。
本篇收录于《offer快到碗里来》
写在之前
"进程和线程有何区别?"
这个问题是校招面试中最最常见的问题了。很多人讨厌这种背诵课本概念的问题,还请看管打住,稍后再喷;该问题还真是一个值得思考的问题。
我们常常挂在嘴边的,你有没有经历过什么高并发项目,有没有比较难以解决的高并发问题。面试时,如果说没有遇到高并发问题似乎低人一等。
既然说到高并发,为啥会有高并发引发的各种问题呢?
本篇待我讲解了线程和进程有何区别,大家再来考虑为何高并发情况下线程安全问题。
最初的计算机时代
也许今天大家对于一个手机既能够刷抖音,又可以逛微博,还可以看***等能力习以为常,但是要知道最初的计算机可是长这样的。
如此大体积的机器,能够计算的能力还不及今天的笔记本。并且最初计算机只能接受一些特定的指令,用户输入一个指令,计算机就做一个操作。用户没有输入,计算机则会等待,用户和计算机总有一个在等待。
后来为了解决这个问题,提出了批处理 思路。用户可以将需要执行的多个程序写在磁带上,然后交由计算机去读取并逐个地执行这些程序,并将输出结果写到另一个磁带上。
但是这样还是有一个问题,如果计算机同时执行两个任务呢,比如一个程序A计算大象有多重,一个程序B计算熊猫有多少根毛,这个过程中计算机需要等待用户的输入(多次I/O操作),计算机只能等待所有的输入好了,再进行计算。这就导致了CPU的浪费。 (CPU又不怕累,闲着干啥)
后续计算机前辈们再想:能否在 任务A 读取数据的过程中,让 任务B 去执行,当 任务A 读取完数据之后,让 任务B 暂停,然后让 任务A 继续执行?反正就一句话,CPU别想偷懒。
但是这样又有一个问题?
在计算机内存中只能保存一份运行中的数据,两个程序相互切的时候如何能够知道彼此上一步计算到哪一步了呢。
为什么需要进程
天才们又发明了进程,让每个进程都有一定的内存地址空间,并且只能使用它自己的内存空间,各个进程间互不干扰。
并且进程可以保存自己的运行状态,这样就为进程切换提供了可能。当进程暂停时,它会保存当前进程的状态(每个进程都有独立的地址空间、堆栈、程序计数器等),在下一次重新切换回来时,便根据之前保存的状态进行恢复,然后继续执行。
举个例子:大家一会在抖音看小姐姐视频,一会儿回女朋友消息,回完了消息,还可以继续在原地方看小姐姐视频。
虽然直观感受上是同时进行的,但是其实同一个时间范围内只能有一个进程在占用一个CPU,只不过CPU执行太快了,导致用户以为是同时进行的。这就是并发,能够让操作系统从宏观上看起来同一个时间段有多个任务在执行。
为什么需要线程
上述听起来似乎一切都很美好,但是人类对于快的追求是无止尽的。爱折腾的计算机专家又在想:一个进程在一个时间段内只能做一件事情,如果一个进程有多个子任务,能不能让这些任务也分开执行呢?让cpu片刻不得休息。
于是人们又发明了线程。每个线程都可以去执行一个子任务,这样一个进程就包括了多个线程,每个线程负责一个独立的子任务。
既然是在 一个进程内,那么多个线程必然共享了进程的独立地址空间或者说数据空间,每个线程控制自己的任务变量(程序计数器)和栈信息(存储各任务的变量信息)
线程让进程中的内部并发成为了可能。
线程引发的问题
可以看到多个线程共享了数据空间(内存),那么又会存在一个问题: 如果多个线程要同时访问某个资源,怎么处理?
比如,有一个花名册上面写了每个人的信息,张三想把自己的数学成绩由61改成99,李四想故意做坏,想把张三数据改得更低,由61改成59,王五更加丧心病狂,想直接删除张三的成绩(好家伙,缺德呀),多个人该同一份数据,如果同时发生,计算机怎么知道该执行谁的命令呢?
这也就是人们考虑的线程安全问题。
你说线程带来的问题大不大?肯定是大的。
但是你要说值得吗?
我觉得是值得的。
因为引入线程带来的收益造福的更多的人,总不能都2021年了,你的电脑做一些复杂的运算还是龟速吧。至于引发的线程问题,那就是coder
们的事儿了。如何写出线程安全的代码,那就是各位看官该考虑的事儿了。
总结
总结一下什么是线程?什么是进程?
- 进程有自己独立的资源和地址空间,而线程可以与其他线程共享资源和内存,每个线程都有各自的 程序计数器 、 栈 以及 局部变量 等等。
- 进程是对运行时程序的封装,可以保存程序的运行状态,实现操作系统的并发;
- 进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能;
- 进程是操作系统资源的分配单位,线程是
CPU
调度的基本单位; - 线程的引入导致了线程安全问题。
如有错误,还望不吝赐教。
参考:
《Java 并发编程实战》
《Java并发编程艺术》
《深入理解计算机系统》