文章目录
-
- 线程和进程的概念区别
- 并行和并发有什么区别
- 解释一下用户态和内核态,什么场景下,会发生内核态和用户态的切换?
- 进程调度算法你了解多少
- 进程间有哪些通信方式
- 解释一下进程同步和互斥,以及如何实现进程同步和互斥
- [什么是死锁, 如何避免死锁](#什么是死锁, 如何避免死锁)
- 介绍一下几种典型的锁
- 讲一讲你理解的虚拟内存
- 你知道的线程同步方式有哪些
- 有哪些页面置换算法
- 熟悉哪些Linux命令
- Linux中如何查看一个进程,如何杀死一个进程,如何查看某个端口有没有被占用
- [说一下 select、poll、epoll](#说一下 select、poll、epoll)
线程和进程的概念区别
-
进程包含线程, 一个进程内有一个或多个线程, 至少要有一个线程
-
进程是系统资源分配的基本单位
线程是系统调度执行的基本单位
-
同一个进程内的线程之间, 共用一部分进程资源(内存, 硬盘, 网络带宽等...)
内存资源, 就是代码中定义的变量/对象...
在编程中, 多个进程, 是可以共用一部分变量的
-
线程是当下实现并发编程的主流方式, 通过多线程, 可以充分利用多核CPU
但是, 不是线程数目越多越好, 当线程数目达到一定程度, 把多个核心充分利用时, 继续增加线程, 无法再提高效率
甚至可能会影响效率(线程调度, 也是有开销的)
-
多个线程之间, 可能会相互影响. 线程安全问题, 一个线程抛出异常, 可能会把其他线程一起带走
多个进程之间, 一般不会相互影响, 不会影响到其他进程
并行和并发有什么区别
并发:
同一个时间段内多个程序交替运行, 但同一时刻可能只有一个程序运行
是通过任务切换实现的, 任务在快速切换, 给人一种同时运行的错觉
并行:
同一个时刻内多个程序同时运行
将任务分配到多核/cpu上实现
并发(单核):
CPU核心: ┌─┬─┬─┬─┬─┬─┐
│A│B│A│B│A│B│ ← 任务交替
└─┴─┴─┴─┴─┴─┘
时间: 1 2 3 4 5 6
并行(多核):
核心1: ┌───┬───┬───┐
│ A │ A │ A │
└───┴───┴───┘
核心2: ┌───┬───┬───┐
│ B │ B │ B │
└───┴───┴───┘
时间: 1 2 3 ← 同一时刻
比如并发, 在一个核心的情况下, 时间1执行A, 时间2执行B...从微观上来看, 同一时刻只执行一个任务, 从宏观上来看, A和B在同一时间段都在推进
比如并行, 在两个核心的情况下, 两个任务A,B在同时执行, 实现了物理上的同时执行
解释一下用户态和内核态,什么场景下,会发生内核态和用户态的切换?
用户态和内核态是操作系统中的两种CPU运行级别,它们定义了程序能够访问的系统资源和执行权限。
用户态是普通应用程序的运行模式, 只能访问分配给自己的虚拟内存, 权限受限但安全
内核态是操作系统内核运行的模式, 可以完全访问硬件资源, 执行CPU指令, 访问整个物理内存
内核态和用户态的切换, 也称上下文切换
系统调用: 应用程序主动请求操作系统服务时触发
中断: 由硬件设备触发
异常: 程序出现错误异常时, CPU会自动切换到内核态, 以便处理这些异常
进程调度算法你了解多少
进程间有哪些通信方式
解释一下进程同步和互斥,以及如何实现进程同步和互斥
互斥: 保证共享资源一次只被同一个进程/线程使用, 解决竞争问题. 比如多个用户操作同一个银行账号
同步: 协调多个进程/线程的执行顺序, 解决协作问题. 比如生产者先生产, 消费者才能消费.
实现的画可以用 java的synchronized关键字
互斥:
┌─────────────────────────────────────────────┐
│ 共享资源(如:打印机) │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 线程1 │ │ 线程2 │ │
│ │ │ │ │ │
│ │ 想用打印机 ──┼──┼─→ 想用打印机 │ │
│ │ │ │ │ │
│ │ 申请锁──────┐ │ │ 申请锁──────┐│ │
│ │ ↓ │ │ ↓│ │
│ └───────────┐ │ │ └───────────┐ ││ │
│ ↓ ↓ ↓ ↓ ↓↓ │
│ ┌──────────────┐ │ │
│ │ 锁(门卫) │ │ │
│ │ 状态: │ │ │
│ │ ☑ 可用 │ │ │
│ │ ☐ 占用 │ │ │
│ └─────┬────────┘ │ │
│ │ │ │
│ ┌───────┴────────┐ │ │
│ │ │ │ │
│ 线程1获得锁 → ☑ │ │ │
│ │ │ ← 线程2等待│ │
│ └─────┬──────────┘ │ │
│ ↓ │ │
│ ┌─────────────┐ │ │
│ │ 临界区 │ │ │
│ │ 使用打印机 │ │ │
│ └─────┬───────┘ │ │
│ ↓ │ │
│ ┌─────────────┐ │ │
│ │ 释放锁 │ │ │
│ │ │ → 唤醒线程2 │ │
│ └─────────────┘ │ │
└─────────────────────────────────────────────┘
"当线程1先到达时,它检查锁的状态,发现是'可用',于是获取锁进入临界区使用资源。"
"这时线程2也到达了,它检查锁发现是'占用'状态,于是必须等待。"
"线程1使用完资源后,释放锁。"
"这时线程2可以获取锁并进入临界区。"
"这样就保证了同一时间只有一个线程访问共享资源。"
java
public synchronized void increment() {
count++;
}
同步:
┌────────────────────────────────────────────────────┐
│ 缓冲区(共享资源) │
│ ┌─────────────────────────────┐ │
│ │ 空│空│满│满│空 │ │
│ │ 容量=5,当前有2个产品 │ │
│ └─────────┬───────────────────┘ │
│ │ │
│ ┌───────┼───────┐ ┌───────┼───────┐
│ │ 生产者线程 │ │ 消费者线程 │
│ │ │ │ │
│ │ while(true) { │ │ while(true) { │
│ │ 生产产品(); │ │ 消费产品(); │
│ │ if(缓冲区满) │ │ if(缓冲区空) │
│ │ wait(); │ │ wait(); │
│ │ 放入产品(); │ │ 取出产品(); │
│ │ notify(); │ │ notify(); │
│ │ } │ │ } │
│ └───────┬───────┘ └───────┬───────┘
│ │ │
│ 需要:缓冲区有空位 需要:缓冲区有产品
└────────────────────────────────────────────────────┘
"这是一个典型的生产者-消费者同步场景:"
"中间是一个有限大小的缓冲区,作为生产者和消费者的共享资源。"
"左边是生产者线程,它负责生产数据放入缓冲区。"
"右边是消费者线程,它从缓冲区取出数据消费。"
"这里有两个关键条件需要协调:"
"第一,当缓冲区满时,生产者必须等待,直到消费者取走数据腾出空间。"
"第二,当缓冲区空时,消费者必须等待,直到生产者放入新的数据。"
"它们通过wait()和notify()机制来协调:"
"生产者放入数据后,会通知等待的消费者'有数据了,可以取了'。"
"消费者取走数据后,会通知等待的生产者'有空位了,可以生产了'。"
"这样就实现了有序的协作。"
java
// 面试经典题:生产者-消费者
class MessageBox {
private String message;
private boolean hasMessage = false;
// 生产者
public synchronized void produce(String msg) {
// 等待消费者取走当前消息
while (hasMessage) {
wait(); // 释放锁,等待通知
}
message = msg;
hasMessage = true;
notifyAll(); // 通知等待的消费者
}
// 消费者
public synchronized String consume() {
// 等待生产者生产消息
while (!hasMessage) {
wait(); // 释放锁,等待通知
}
hasMessage = false;
notifyAll(); // 通知等待的生产者
return message;
}
}
什么是死锁, 如何避免死锁
死锁是是指两个或多个进程/线程在执行过程中, 由于竞争资源造成相互等待的现象
死锁的场景有
一个线程, 一个锁加两次:
在获取第一个锁之后, 这个锁已占用, 再获取这一个锁, 要等这个锁释放, 等这个锁释放, 需要走完整个代码才能释放, 所以会一直卡在这里, 陷入等待
两个线程, 两把锁:
线程1拿到锁A, 线程2拿到锁B 线程1再去拿B, 线程2再去拿A
线程2拿A需要等A的释放, 而A的释放需要线程1拿到B之后走完, 1拿到B需要线程2拿到A走完, 两个线程就会永远阻塞
N个线程,M把锁 哲学家就餐:
就是上一个两个线程两把锁的扩大版
有五个哲学家, 5根筷子, 当他们全都拿到左手的筷子时, 就会陷入等待
解决办法是
死锁的四个必要条件:
- 锁是互斥的
- 锁是不可抢占的 线程1拿到了锁A, 如果线程1不主动释放A, 线程2不能把锁A抢过来
- 请求和保持 线程1, 拿到锁A之后, 不释放A的前提下, 去拿锁B
- 循环等待 多个线程获取锁的过程, 存在循环等待
由于1和2是锁的特性, 无法改变
就要修改3和4
3是避免出现锁的嵌套, 先释放锁A再拿到锁B, 就能解决这个问题
4是给锁加编号, 并约定加锁顺序, 规定统一的拿锁顺序, 先拿小编号锁, 再拿大编号锁, 就能打破锁的循环等待
介绍一下几种典型的锁
乐观锁 vs 悲观锁
轻量级锁 vs 重量级锁
自旋 vs 挂起等待锁
可重入 vs 不可重入锁
公平 vs 非公平锁
读写锁
讲一讲你理解的虚拟内存
它不是真实存在的,而是通过映射与实际物理地址空间
整个进程地址空间都是"虚拟内存"。当你运行一个程序时,操作系统加载器会为你搭建一个完整的、独立的、连续的地址空间供你使用
你知道的线程同步方式有哪些
线程同步机制是指在多线程编程中,为了保证线程之间的互不干扰,而采用的一种机制。常见的线程同步机制有以下几种:
- 互斥锁:互斥锁是最常见的线程同步机制。它允许只有一个线程同时访问被保护的临界区(共享资源)
- 条件变量:条件变量用于线程间通信,允许一个线程等待某个条件满足,而其他线程可以发出信号通知等待线程。通常与互斥锁一起使用。
- 读写锁: 读写锁允许多个线程同时读取共享资源,但只允许一个线程写入资源。
- 信号量:用于控制多个线程对共享资源进行访问的工具。