2011 年 09 月 01 日 by name5566
(对原文进行部分修改)
从 Win32 的角度出发,进程持有内存和资源(进程提供了一个安置内存和资源的地方):
- 内存:理论上可以到 2G(X64就大的多了,因为最近搞了x64)
- 资源:包括内核对象(如文件和线程)、用户资源(如对话框和字符串)、GDI 资源(如 DC 和 Brushes)
Win32 生成进程的代价比 Unix 高一个数量级(《Unix 编程艺术》),从而影响到上层开发者的开发方式(详见《Unix 编程艺术》Unix 哲学同其他哲学的比较)。线程比进程廉价(节省系统资源,创建和销毁速度快)。Win32 产生进程的效率确实比 Unix 低,但是它产生线程的效率却比 Unix 高。
内存和进程关系非常紧密,内存大致分为 3 种类型:
- Code:只读并且是 CPU 唯一允许执行的内存
- Data:程序中所有的非局部变量(全局变量和静态变量),线程还可以使用 malloc 或者 new 来动态分配内存
- Stack:调用函数时的 Stack(堆栈),包含局部变量,每个线程产生时配有一个堆栈
这些内存对于进程中的所有线程都是可用的。进程其实没有真正做事情,真正做事的是线程。
只要涉及到代码的执行,就有线程的存在,同一时间可以有多个线程执行相同的代码。线程的状态保存:
- 保存在进程的某个内存区域
- CPU 的寄存器上(如 ESP、EIP 等)
线程的一些重要数据(变量、调用栈等)保存在进程中那些可以被其他线程共享的内存中。Win32 因为安全的原因,进程的资源是不可以直接被其他进程访问,而进程中的资源能够被进程中的线程所共享。
关于使用多线程还是使用多进程来解决某个问题,这取决于这个具体的问题,没有任何一个是万能药。
使用线程关键要理解的一个问题就是 Context Switch:在像 Win32 这样的抢占式多任务的操作系统中,在一个线程执行了足够久之后,硬件计时器会发出一个中断让 CPU 去获取当前线程的状态(拷贝寄存器的内容到 CONTEXT 结构中去)。在切换到另一个线程去时:
- 操作系统首先切换该线程隶属的进程的内存(Switch 的两个线程可能处于也可能不处于同一个进程中),也就是切换 Memory Context(其包括 Page directory 和 Page tables)
- 然后恢复寄存器(CONTEXT 结构的值恢复到 CPU 寄存器中去)
上述的整个过程就是 Context Switch。
属于不同的进程的两个线程不能直接共享内存,同一个进程的两个线程将共享所有内存。同一个地址在两个不同的进程中实际上指向的是不同的物理内存区域。
非适当的使用多线程,其效率比单线程更低。例如:使用一个单核 CPU 计算圆周率后百万位,用一个线程计算两次的时间假定为 T,那么同时使用两个线程但每个线程只计算一次的时间为 T + Ta。多出来的时间 Ta 主要是花费在 Context Switch 上。但是如果你的 CPU 有多个核(假定此 CPU 一个核和上述 CPU 的计算能力相同),那么上述情况就不一样了,同时使用两个线程但每个线程只计算一次的时间为 T / 2。
要注意的是,一条 C/C++ 代码在编译后为可能为多条机器指令,因此在执行一条 C/C++ 代码并还未执行完成时就可能发生 Context Switch。因此,我们需要原子操作(一个操作能够不受中断的完成,那么这个操作就是原子操作)。
通常来说,多线程程序的编写是复杂的(两个人同时炒一盘菜不是一件容易的事情)。