异步的核心特征
- 无需等待当前任务执行完毕,系统可以随时打断当前任务并介入处理其他事务。
单程序的并发实现方案
单程序实现并发,既可以使用多线程,也可以使用 fork() 创建多进程。
一、 多线程 (Multi-threading)
- 资源与开销: 线程之间共享所属进程的内存空间和系统资源,线程间的通信成本极低(可以直接读写共享变量),且创建和上下文切换的开销远小于进程。
- 适用场景(语言差异与限制):
- Python / Ruby (受限于 GIL): 因为全局解释器锁(GIL)的存在,多线程无法真正利用多核 CPU 进行并行计算。在这种情况下,用多线程处理计算密集型任务,反而会因为频繁的上下文切换导致变慢。(对于 Python,计算密集型任务必须用多进程解决)
- C++ / Java / Go 等: 在这些语言中,多线程确实可以利用多核进行并行计算。但如果线程之间需要频繁同步(如频繁加锁),计算效率也会大幅下降。
- 多线程的最佳场景: I/O 密集型任务(例如网络爬虫、读写文件、网络请求)。因为线程在等待 I/O 响应时,可以主动释放 CPU 控制权给其他线程,且线程切换成本低。
- 虽然通信成本低(直接读写共享变量),但开发者必须手动处理线程安全问题 (Race Condition)。为了防止数据撞车,必须引入锁机制(Mutex / Lock)。而复杂的锁逻辑不仅容易导致死锁,还会成为性能瓶颈。
二、 多进程 (Multi-processing)
- 核心优势: 进程间资源完全隔离。一个子进程的崩溃不会影响父进程或其他子进程,因此具有极高的稳定性。同时,多进程能完美绕过 GIL 限制,充分利用多核 CPU 的并行计算能力。
- 适合任务相对独立、需要高稳定性的场景(例如 Web 服务器的请求处理模块、批量任务的隔离执行)。
- 开销与写时复制技术:
- 创建开销: 使用
fork()启动新进程需要复制页表、文件描述符表等,其创建开销远大于启动一个线程。 - 写时复制 (COW - Copy-On-Write): Linux 的
fork()采用了 COW 技术。在子进程创建之初,并不会真正复制父进程的所有物理内存。只有当父/子进程试图修改某个数据时,操作系统才会真正进行该内存页的物理复制。这使得fork()瞬间的开销比预想的要小很多。
- 创建开销: 使用
- 进程间通信 (IPC - Inter-Process Communication):
- 由于内存隔离,子进程修改了变量(如
count),仅仅是修改了自己内存空间里的副本,父进程对此一无所知。 - 有共同祖先的进程:可以通过通道 (Pipe) 进行通信。
- 无共同祖先的进程:可以通过信号 (Signal)、命名管道 (FIFO)、信号量 (Semaphore)、共享地址空间或消息队列 (Message Queue) 进行通信。
- 通信成本: 很多高级语言在实现 IPC 时,涉及到数据的序列化(如 Python 的 Pickle)和反序列化,这个过程非常消耗 CPU 资源。
- 由于内存隔离,子进程修改了变量(如