操作系统
进程是资源分配的最小单位,线程是 CPU 调度的最小单位。 一个进程拥有独立的内存空间(代码段、数据段、堆),进程之间互相隔离,一个崩溃不影响另一个。而一个进程内的多个线程共享该进程的内存,各自只独占栈和寄存器。
YARN 的 Container 本质是对进程资源的封装;Spark 的 Executor 是常驻进程、Task 以线程方式跑在里面(这正是它比 MapReduce 每个 Task 起一个 JVM 进程快的原因之一);跨节点通信则全部落到 Socket 上。
这个共享带来了两面性:
- 好处是通信便宜。同进程内的线程直接读写同一块内存就能交换数据,不需要任何额外机制;进程间则必须借助专门的手段。
- 代价是必须处理竞态。多个线程同时改同一个变量,结果取决于执行顺序,于是有了锁、信号量这一整套同步机制。
切换开销也差得远:进程切换要换页表、刷 TLB、换整个地址空间;线程切换只换寄存器和栈指针。
进程在生命周期里主要在三个状态间转换:
| 1 | 就绪(拿到了除 CPU 外的所有资源,排队等调度) |
| 2 | ↕ |
| 3 | 运行(正在 CPU 上执行) |
| 4 | ↓ |
| 5 | 阻塞(等 IO、等锁、等信号,此时让出 CPU) |
关键区别是就绪和阻塞都不占用 CPU,但性质完全不同:就绪态只差 CPU,调度到就能跑;阻塞态即使给它 CPU 也跑不了,必须等外部事件(比如磁盘读完)。所以调度器只从就绪队列里挑,阻塞的进程要先被唤醒变成就绪。
加上创建和终止两个瞬时状态,就是常说的五状态模型。
因为进程内存隔离,交换数据必须走操作系统提供的通道。常见几种,按「数据量」和「是否需要同步」来选:
| 方式 | 特点 |
|---|---|
| 管道 | 半双工、亲缘进程间,简单但只能单向流 |
| 消息队列 | 有格式的消息,可异步,内核维护 |
| 共享内存 | 最快,直接映射同一块物理内存,无需拷贝;但必须自己配合信号量做同步 |
| 信号量 | 本身不传数据,是同步原语,常配合共享内存使用 |
| Socket | 唯一能跨机器的,分布式系统的基础 |
共享内存快是因为它省掉了「用户态 → 内核态 → 用户态」的两次数据拷贝,其他方式都逃不掉这一步。
- 定义:指多个线程因竞争共享资源而造成的一种僵局,若无外力作用,这些线程都将永远不能再 向前推进。
- 产生原因:竞争系统资源、进程推进顺序非法。
- 产生死锁必要条件:互斥条件、请求和保持条件、不剥夺条件、循环等待条件。
- 解决方法:预防、避免(银行家算法)、检测(资源分配表)、解除(剥夺资源、撤销进程)。
- 悲观锁基于悲观的假设,认为共享资源在每次访问时都会发生冲突,因此在每次操作时都会加锁。这种锁机制会导致其他线程阻塞,直到锁被释放。Java 中的
synchronized和ReentrantLock是悲观锁的典型实现方式。虽然悲观锁能有效避免数据竞争,但在高并发场景下会导致线程阻塞、上下文切换频繁,从而影响系统性能,并且还可能引发死锁问题。 - 乐观锁基于乐观的假设,认为共享资源在每次访问时不会发生冲突,因此无须加锁,只需在提交修改时验证数据是否被其他线程修改。Java 中的
AtomicInteger和LongAdder等类通过 CAS(Compare-And-Swap)算法实现了乐观锁。乐观锁避免了线程阻塞和死锁问题,在读多写少的场景中性能优越。但在写操作频繁的情况下,可能会导致大量重试和失败,从而影响性能。 - 乐观锁主要通过版本号机制或 CAS 算法实现。版本号机制通过比较版本号确保数据一致性,而 CAS 通过硬件指令实现原子操作,直接比较和交换变量值。
乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。
一般是在数据表中加上一个数据版本号
version字段,表示数据被修改的次数。当数据被修改时,version值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的 version 值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
举一个简单的例子:假设数据库中帐户信息表中有一个 version 字段,当前值为 1;而当前帐户余额字段(balance)为 $100。
- 操作员 A 此时将其读出(
version=1),并从其帐户余额中扣除 $50($100-$50)。 - 在操作员 A 操作的过程中,操作员 B 也读入此用户信息(
version=1),并从其帐户余额中扣除 $20($100-$20)。 - 操作员 A 完成了修改工作,将数据版本号(
version=1),连同帐户扣除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本等于数据库记录当前版本,数据被更新,数据库记录version更新为 2。 - 操作员 B 完成了操作,也将版本号(
version=1)试图向数据库提交数据(balance=$80),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 1,而数据库记录当前版本为 2,不满足 “ 提交版本必须等于当前版本才能执行更新 ” 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员 A 的操作结果的可能。
CAS 的全称是 Compare And Swap(比较与交换),用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。
CAS 涉及到三个操作数:
- V:要更新的变量值(Var)
- E:预期值(Expected)
- N:拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。
举一个简单的例子:线程 A 要修改变量 i 的值为 6,i 原值为 1(V = 1,E=1,N=6,假设不存在 ABA 问题)。
- i 与 1 进行比较,如果相等, 则说明没被其他线程修改,可以被设置为 6。
- i 与 1 进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更新,CAS 操作失败。
当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。
悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
- ls:列出目录内容,加
-l显示详细信息,-a显示隐藏文件 。 - cd:切换目录,
cd ..返回上一级,cd ~回到家目录 。 - pwd:显示当前所在的绝对路径 。
- cp/mv/rm:分别用于复制、移动/重命名、删除文件,
rm -rf极度危险需慎用 。 - mkdir/touch:创建目录或空文件 。
- cat:一次性输出小文件全部内容 。
- less/more:分页查看大文件,支持上下翻页和搜索 。
- head/tail:查看文件开头或结尾,默认显示 10 行,
tail -f可实时跟踪日志 。 - grep:在文件中搜索关键词,支持递归搜索目录 。
- ps/top:查看运行中的进程,
top可动态监控资源占用 。 - df/du:查看磁盘空间占用和目录大小,常用
-h以人类可读方式显示 。 - free:查看内存使用情况 。
- netstat/ss:查看网络连接和端口占用情况 。
登录后可以选中正文添加批注(仅自己可见)。
评论 (0)
登录后参与评论。
还没有评论,来做第一个。