多线程最显著的好处是:
- 更好的 CPU 利用率。
- 在某些情况下程序设计更简单。
- 响应性更强的程序。
- 在不同任务之间更公平地分配 CPU 资源。
更好的 CPU 利用率
想像一下,有个应用程序从本地文件系统读取和处理文件。假设从磁盘读取一个文件要 5 秒钟,处理它要 2 秒钟。那么处理两个文件需要花:
5 秒钟读文件 A
2 秒钟处理文件 A
5 秒钟读文件 B
2 秒钟处理文件 B
-----------------------
总共 14 秒
当从磁盘读取文件时,CPU 的大部分时间都花在等待从磁盘读数据上。CPU 在这段时间内几乎处于空闲状态。其实它是可以做其它事情的。调整一下操作顺序,就可以更好地利用 CPU。看看这个顺序:
5 秒钟读文件 A
5 秒钟读文件 B + 2 秒钟处理文件 A
2 秒钟处理文件 B
-----------------------
总共 12 秒
CPU 等待读完第一个文件,然后开始读第二个文件。当计算机的 IO 组件读第二个文件时,CPU 处理第一个文件。请记住,在等待从磁盘读取文件时,CPU 大多处于空闲。
通常,CPU 在等待 IO 时可以做其它事情。它不一定是磁盘 IO,可以是网络 IO,也可以是机器上的用户输入。网络和磁盘 IO 通常比 CPU 和内存 IO 慢得多。
更简单的程序设计
如果要在一个单线程应用程序中手工编写上述读取和处理顺序,就必须跟踪每个文件的读取和处理状态。相反,我们可以启动两个线程,每个线程只读取和处理一个文件。在等待磁盘读取其文件时,每个线程都将被阻止。在等待时,其他线程可以使用 CPU 来处理它们已经读取的文件部分。结果是,磁盘始终处于繁忙状态,从各种文件读取到内存中。这样可以更好地利用磁盘和 CPU。编程也更容易,因为每个线程只需要跟踪一个文件。
更具响应性的程序
将单线程应用程序转换为多线程应用程序的另一个常见目标是实现更具响应性的应用程序。设想一个服务器应用程序在某个端口上监听传入的请求。当收到请求时,它会处理请求,然后返回兼通。服务器循环如下图所示:
while(server is active){
监听请求
处理请求
}
如果处理请求需要很长时间,那么在此期间,服务器就没法接收新的客户端发送的请求。只有在服务器监听时才能接收请求。
另一种设计是监听线程将请求传递给工作线程,然后立即返回监听。工作线程将处理请求,并向客户端发送回复。该设计概述如下:
while(server is active){
监听请求
将请求转给工作线程
}
这样服务器线程就可以更快地恢复监听。因此,更多的客户端可以向服务器发送请求。服务器的响应能力增强了。
桌面应用程序也是如此。如果单击一个启动长任务的按钮,而执行该任务的线程是更新窗口、按钮等的线程,那么在任务执行时,应用程序将显示为无响应。相反,如果把任务交给工作线程,那么当工作线程忙于任务时,窗口线程可以自由地响应其他用户请求。当工作线程完成时,它向窗口线程发出信号。然后,窗口线程可以用任务的结果更新应用程序窗口。采用这种工作线程设计的程序,对于用户来说就有了更好的响应性。
更公平地分配 CPU 资源
假设一个服务器正在接收客户端的请求。想象一下,其中一个客户端发送了一个需要很长时间才能处理的请求,例如 10 秒。如果服务器只用一个线程处理所有任务,那么这个处理缓慢的请求之后的所有请求就都将被迫等待,直到这个请求处理完。
通过在多个线程之间分配 CPU 时间,并在线程之间切换,CPU 就可以更公平地在多个请求之间共享其执行时间。然后,即使其中一个请求很慢,处理速度更快的其他请求也可以与速度较慢的请求同时执行。当然,这意味着执行慢请求的速度会更慢,因为不会把 CPU 单独分配给处理它。但是,其他请求必须等待更短的时间才能被处理,因为它们不必等到慢任务完成后才能被处理。如果只有慢请求需要处理,那么 CPU 仍然可以单独分配给慢任务。