1. 并发编程的演进背景
计算机系统的发展史就是一部不断追求更高资源利用率的奋斗史。早期的计算机系统只能串行执行任务,CPU大部分时间都在等待I/O操作完成,这种低效的资源利用方式催生了并发编程的需求。
我第一次真正理解并发的重要性是在开发一个电商促销系统时。当时我们的服务器配置相当不错,但面对瞬时爆发的用户请求,系统响应速度却慢得令人难以接受。通过性能分析发现,传统的多线程模型在大量I/O等待时效率极低,CPU利用率长期低于30%。这正是推动我们研究更高效并发模型的现实动力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 进程:资源隔离的基本单元
2.1 进程的本质与组成
进程是操作系统资源分配的基本单位,它代表了一个正在执行的程序实例。每个进程都拥有独立的虚拟地址空间、文件描述符表和各种系统资源。这种隔离性使得一个进程的崩溃不会影响其他进程,为系统稳定性提供了基础保障。
从技术实现角度看,一个进程主要由以下几部分组成:
- 代码段:存放可执行指令
- 数据段:存放全局变量和静态变量
- 堆:动态内存分配区域
- 栈:函数调用栈,存放局部变量和返回地址
- 进程控制块(PCB):操作系统维护的进程元数据
2.2 进程上下文切换的代价
进程切换是代价高昂的操作,涉及以下步骤:
- 保存当前进程的CPU上下文(寄存器值、程序计数器等)
- 更新进程控制块(PCB)状态
- 切换内存地址空间(包括TLB刷新)
- 恢复新进程的CPU上下文
- 更新内存管理单元(MMU)设置
在我的性能测试中,一次完整的进程上下文切换在Linux系统上大约需要3-5微秒。这个开销看起来不大,但在高并发场景下累积起来非常可观。例如,一个每秒处理10万请求的系统,如果每个请求都涉及进程切换,仅切换开销就会消耗30-50%的CPU时间。
3. 线程:轻量级执行单元
3.1 线程模型的演进
线程的出现是为了解决进程切换开销过大的问题。在Linux系统中,线程经历了从"重量级进程"到"轻量级进程"的演进:
- 早期实现:通过clone()系统调用创建,每个线程仍对应一个内核调度实体
- NPTL(Native POSIX Thread Library):引入线程组概念,优化了同步和通信机制
- 现代实现:采用1:1模型,每个用户线程对应一个内核线程
注意:虽然Linux线程实现已经相当高效,但在创建数万个线程时仍会遇到性能瓶颈,这是因为每个线程都需要内核资源支持。
3.2 线程同步的陷阱
在多线程编程中,同步是最容易出问题的环节。以下是我在实际项目中总结的经验:
锁的使用原则:
- 尽量缩小临界区范围
- 避免在临界区内执行耗时操作
- 注意锁的获取顺序,防止死锁
- 考虑使用读写锁替代互斥锁
常见问题案例:
java复制// 反例:在同步块内执行IO操作
synchronized(lock) {
St
