单线程和多线程
单线程和多线程,本质是在讲:一个程序里有几条执行路线。
可以先记住一句话:
单线程:一个人干活,一次只能做一件事。 多线程:多个人一起干活,可以同时推进多件事。但这里的“同时”要分清楚:有时是真同时,有时只是切换得很快,看起来像同时。
一、先理解进程和线程
一个程序运行起来后,操作系统会给它分配资源,这个运行中的程序叫:
进程 Process比如你打开 Chrome、微信、VS Code,它们都是进程。
线程是进程里的执行单位:
线程 Thread可以这样理解:
进程 = 一个公司 线程 = 公司里的员工一个进程至少有一个线程。这个默认线程通常叫主线程。
线程之间共享同一个进程里的资源,比如内存、文件句柄、网络连接等。但每个线程也有自己的调用栈、执行位置、局部变量等。
二、单线程是什么
单线程就是一个进程里主要只有一条执行路线。
任务 A -> 任务 B -> 任务 C -> 任务 D一次只能执行一个任务。前一个任务没执行完,后一个任务不能真正开始执行。
如果doSomethingSlow()很耗时,那么console.log("B")要等它结束后才能执行。
单线程的特点:
执行顺序简单
不容易出现数据竞争
不需要复杂的锁
调试相对容易
但一个任务卡住,后面的任务都会受影响
典型例子:
浏览器里的 JavaScript 主线程
Redis 早期核心命令处理模型
简单脚本程序
很多 UI 程序的主线程
三、单线程的优点
单线程最大的优点是简单。
因为只有一条执行线,所以你不用担心两个线程同时修改同一个变量
单线程里结果很明确,最后就是2。
它不会出现“两个线程同时读到旧值,然后互相覆盖”的问题。
所以单线程程序通常:
思路清晰
状态管理简单
不容易死锁
不需要频繁加锁
行为更可预测
四、单线程的缺点
单线程的问题也很明显:怕卡。
如果一个任务执行很久,整个线程都会被占住。
比如浏览器中:
while (true) { // 死循环 }这个代码会让页面卡死。因为 JavaScript 主线程被死循环占住了,浏览器没机会处理点击、滚动、渲染等任务。
单线程常见问题:
一个慢任务会拖住所有后续任务
无法充分利用多核 CPU
CPU 密集型任务容易卡顿
阻塞式 I/O 会严重影响响应能力
五、多线程是什么
多线程就是一个进程里有多条执行路线。
线程 1:任务 A -> 任务 B 线程 2:任务 C -> 任务 D 线程 3:任务 E -> 任务 F这些线程可以同时推进不同任务。
如果电脑有多个 CPU 核心,多个线程可以真正并行运行。
比如:
CPU 核心 1 执行线程 A CPU 核心 2 执行线程 B CPU 核心 3 执行线程 C这就是真正的并行。
如果只有一个 CPU 核心,多线程也可以运行,但不是同时执行,而是操作系统快速切换:
线程 A 执行一会儿 切到线程 B 执行一会儿 再切到线程 C 执行一会儿 再切回线程 A这种叫并发,不是真正并行。
六、多线程的优点
多线程适合把任务分开做。
比如一个图像处理程序:
线程 1:处理图片上半部分 线程 2:处理图片下半部分如果有多个 CPU 核心,速度可能明显提升。
再比如一个桌面软件:
主线程:负责界面响应 后台线程:负责下载文件 后台线程:负责解析数据 后台线程:负责保存日志这样界面不会因为下载文件而卡死。
多线程的优点:
可以利用多核 CPU
可以让耗时任务放到后台
可以提高程序响应速度
可以同时处理多个请求
适合 CPU 密集型任务和后台任务
七、多线程的缺点
多线程最大的问题是复杂。
因为多个线程共享同一块内存,所以它们可能同时修改同一个数据。
比如:
count = 0 线程 A:读取 count,得到 0 线程 B:读取 count,得到 0 线程 A:count + 1,写回 1 线程 B:count + 1,写回 1你以为两个线程各加了一次,结果应该是2。
但最后可能是:
count = 1这就是数据竞争,也叫竞态条件。
多线程常见问题:
数据竞争
死锁
活锁
线程饥饿
调试困难
锁竞争导致性能下降
上下文切换带来额外开销
八、什么是锁
为了解决多个线程同时修改共享数据的问题,需要加锁。
比如:
线程 A 要改 count,先拿锁。 线程 A 改完后释放锁。 线程 B 才能拿锁,再修改 count。锁可以保证同一时间只有一个线程进入关键区域。
但锁也有代价:
写起来麻烦
用错可能死锁
锁太多会降低性能
锁粒度不好会影响并发效果
九、死锁是什么
死锁就是多个线程互相等对方释放资源,结果谁都走不下去。
比如:
线程 A 拿到了锁 1,等待锁 2 线程 B 拿到了锁 2,等待锁 1结果:
A 等 B B 等 A 两个都卡住这就是死锁。
十、 并发和并行
单线程、多线程还要和并发、并行区分开。
并发:
多个任务都在推进,但不一定同一瞬间执行。并行:
多个任务真的在同一瞬间执行。单核 CPU 上的多线程:
并发,但不并行多核 CPU 上的多线程:
可以并发,也可以并行单线程异步:
可以并发,但通常不并行