当前位置: 首页 > news >正文

操作系统学习笔记——[特殊字符]超详细 | 如何唤醒被阻塞的 socket 线程?线程阻塞原理、线程池、fork/vfork彻底讲明白!

💡超详细 | 如何唤醒被阻塞的 socket 线程?线程阻塞原理、线程池、fork/vfork彻底讲明白!

    • 一、什么是阻塞?为什么线程会阻塞?
    • 二、socket线程被阻塞的典型场景
      • 🧠 解法思路:
    • 三、线程的几种阻塞状态和唤醒方式一览
    • 四、如何判断线程是繁忙还是阻塞?
    • 五、就绪状态线程在等待什么?
    • 六、如何实现线程池?
      • 🏗 线程池原理:
      • 💡 Java 中线程池核心类:
    • 七、fork 和 vfork 的区别?
      • 🧠 补充:写时复制(COW)
    • 八、进阶:写时复制(COW)原理
    • 九、Server 阻塞状态示意
    • 🔚 总结

一、什么是阻塞?为什么线程会阻塞?

线程阻塞是一种等待某个事件发生的状态。比如等待 I/O 完成、锁释放、条件满足、子线程结束等。

常见导致线程阻塞的情况有:

阻塞方式常见场景唤醒方式
Thread.sleep(ms)让出 CPU 一段时间时间到了自动唤醒
Object.wait()等待被 notify()notify() / notifyAll() 唤醒
Thread.join()主线程等待子线程结束子线程执行完毕自动唤醒
LockSupport.park()显式挂起线程调用 unpark(Thread) 唤醒
socket accept() / read()阻塞等待客户端连接/数据客户端连接/发送数据
synchronized 锁竞争等待锁资源锁释放后参与竞争

二、socket线程被阻塞的典型场景

举个最常见的 ServerSocket 场景:

ServerSocket server = new ServerSocket(8888);
Socket client = server.accept(); // 这里会阻塞直到有客户端连接

当执行 accept() 时,线程会阻塞等待客户端连接,直到有连接进入,线程才会被唤醒继续执行。

📌 问题:如果我想手动唤醒这个被阻塞的 accept() 怎么办?

🧠 解法思路:

  1. 关闭 socket:关闭 ServerSocket,会抛出异常从 accept() 中跳出。
  2. 使用 selector/epoll 实现非阻塞,比如 NIO
  3. 新建连接触发 accept() 返回:可以在本地建立一个 loopback 连接触发 accept() 返回。
// 触发唤醒 server.accept()
Socket socket = new Socket("localhost", 8888);

三、线程的几种阻塞状态和唤醒方式一览

阻塞方式描述唤醒方式示例
Thread.sleep()睡眠,释放 CPU,不释放锁时间到Thread.sleep(1000)
Object.wait()等待 notify,释放锁notify()/notifyAll()obj.wait()
Thread.join()等待其他线程结束子线程结束t.join()
Thread.suspend()(已弃用)挂起线程resume()慎用
LockSupport.park()显式挂起unpark()常用于 AQS
socket.accept()阻塞等待连接有连接到来 / 被关闭

四、如何判断线程是繁忙还是阻塞?

  • Linux 可用 ps -e -o pid,state,cmd 查看线程状态:
状态码描述
RRunning(可运行)
SSleeping(可中断阻塞)
DUninterruptible sleep(不可中断阻塞)
ZZombie
TStopped(暂停)

👀 繁忙线程处于 Running 状态
😴 阻塞线程常见为 Sleeping 或 D 状态


五、就绪状态线程在等待什么?

就绪(Runnable)状态的线程,已经满足了运行条件,但等待操作系统调度器为它分配 CPU 才能执行。它处在一个“抢票”的阶段。


六、如何实现线程池?

线程池是一种线程复用机制,用于提高系统并发能力和资源利用率。基本思路如下:

🏗 线程池原理:

  1. 初始化 N 个工作线程,进入等待状态
  2. 维护一个任务队列(生产者-消费者模型)
  3. 有任务时唤醒线程执行,执行完毕后重新等待
  4. 没任务时线程阻塞等待

💡 Java 中线程池核心类:

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> doTask());

自己实现线程池的基本结构如下:

// 工作线程不断从任务队列中拉取任务
while (true) {
    Runnable task = taskQueue.take(); // 阻塞等待任务
    task.run();
}

七、fork 和 vfork 的区别?

特性fork()vfork()
共享地址空间❌ 否✅ 是
复制页表✅ 复制(写时复制)❌ 不复制
父子并发✅ 并发❌ 父进程阻塞等待子进程执行完
安全性高(地址独立)低(共享,容易出错)
速度稍慢更快(节省内存)

🧠 补充:写时复制(COW)

现代 fork() 实现采用 Copy-On-Write 技术,父子进程共享内存页,只有在子进程写入内存时才真正复制,提高效率。


八、进阶:写时复制(COW)原理

  1. fork 后父子共享内存页,只读
  2. 子进程试图写内存 → 触发页保护异常
  3. 内核复制对应页,子进程写副本
  4. 父进程继续使用原来的页

🌟 优点:节省时间和内存,尤其适用于 fork 后马上执行 exec 的情况


九、Server 阻塞状态示意

Client                  Server
   |                       |
   |---- connect() ----->  | (accept 阻塞)
   |                       |====> 唤醒 accept()
  • Server 端 accept() 无客户端连接时处于阻塞状态
  • 被连接时唤醒进入 RUNNABLE
  • 若使用 epoll,Server 线程始终活跃,但非阻塞式等待事件

🔚 总结

本教程详尽梳理了线程阻塞与唤醒机制,尤其是 socket 阻塞处理、线程池设计、fork/vfork 差异 等关键知识点。以下是学习建议:

  • 掌握阻塞类型:主动、被动、锁等待、IO 等
  • 熟练使用 ps/top/jstack 等工具分析线程状态
  • 实践线程池模型:自己写一个简易线程池
  • 深入理解操作系统中的进程管理机制(fork, vfork, exec)

相关文章:

  • 【PCIE736-0】基于 PCIE X16 总线架构的 4 路 QSFP28 100G 光纤通道处理平台
  • DDoS攻防实战指南——解析企业级防护五大解决方案
  • leetcode03 -- 武汉旅游查询系统
  • 关于setTimeout输出
  • 面试篇 - Transformer模型中的位置编码
  • Windows 操作系统 - Windows 10 磁盘管理无法为 C 盘选择扩展卷
  • Java单例模式:实现全局唯一对象的艺术
  • Linux Kernel 3
  • LDAP渗透测试
  • java 线程池:IO密集型的任务(CPU核数 * 2 + 1),为什么这么设置,计算密集型任务( CPU核数+1 ),为什么这么设置
  • 火车头采集动态加载Ajax数据(无分页瀑布流网站)
  • Python numpy 与pandas
  • Apache Commons CLI 入门教程:轻松解析命令行参数
  • 运维面试题(十三)
  • linux一次启动多个jar包
  • 一键解锁Landsat 9地表温度计算!ENVI与ArcGIS Pro全流程详解(无需NASA大气校正)
  • 解决前端使用Axios时的跨域问题
  • 《MySQL从入门到精通》
  • 【数据集】上市公司投资效率及非效率投资数据测算+dofile(2000-2023年)
  • 深入理解计算机系统记录
  • 上海杨浦:鼓励龙头企业与高校共建创新联合体,最高支持200万元
  • 俄土外长通话讨论俄乌谈判问题
  • 青海规范旅游包车行为:不得引导外省籍旅游包车违规驻地运营
  • 石家庄推动城市能级与民生福祉并进
  • 马鞍山市原常务副市长黄化锋一审获刑11年,涉案金额三千余万元
  • 习近平将出席中国—拉美和加勒比国家共同体论坛第四届部长级会议开幕式并发表重要讲话