02、多线程的好处(CPU使用,程序设计,程序响应,资源分配)

作者:Jakob Jenkov,2019-11-20
翻译:GentlemanTsao,2020-4-11

文章目录

    • 更好的使用CPU
  • 让程序设计更简单
  • 让程序更好的响应
  • 让CPU资源在不同任务间分配的更均衡

多线程最明显的好处有:
更好的使用CPU;
某些情况下让程序设计更简单;
让程序更好的响应;
让CPU资源在不同任务间分配的更均衡。

更好的使用CPU

设想有这样一个程序,它从本地文件系统中读文件并处理。我们假定从硬盘中读一个文件需要5s,然后处理这个文件需要2s。所以处理两个文件就需要:
读文件A,5s
处理文件A,2s
读文件B,5s
处理文件B,2s
总共14s。

在从硬盘读文件的时候,CPU大部分时间都在等待硬盘读取数据,所以CPU多数时间处于空闲状态。我们可以改变操作顺序从而更好的使用CPU,比如看下面的顺序:
读文件A,5s
读文件B,5s + 处理文件A,2s
处理文件B
总共12s

CPU先等待读取第一个文件,接着开始读第二个文件。当计算机IO设备在读第二个文件时,CPU处理第一个文件。别忘了,CPU等待从硬盘读文件的时候,几乎处于空闲状态。

总之,CPU在等待IO时还可以处理其他事情。不一定是磁盘IO,也可以是网络IO或者本机用户的输入。网络和磁盘IO通常比CPU和内存IO要慢的多。

让程序设计更简单

假如你要手工编写一个单线程的程序来实现上面的读取和处理文件的次序,你就需要跟踪每个文件的读取和处理状态。然而你也可以开启两个线程,每个线程仅负责读取和处理一个单独的文件。在等待磁盘读取文件的时候,这个线程会阻塞,而其他线程就可以让CPU处理已经读到的部分文件。于是,磁盘一直保持在工作状态,读取大量的文件到内存中。这样一来便更好的使用了磁盘和CPU,也更易于编程,因为每个线程只需要跟踪一个单独的文件。

让程序更好的响应

把单线程程序改为多线程程序的另一个常见目的,是为了让程序更好的响应。设想一个监听端口请求的服务端程序。它接收到服务并处理该请求,然后继续监听。服务端大概是这样的循环:

whie(server is active){
 监听请求
 处理请求
}

如果请求要花很长时间处理,那在这段时间里,其他用户则无法再发送请求。因为请求只有在服务端监听时才能被收到。

另一种设计是由监听线程把请求传递给工作线程,之后立即返回到监听状态。工作线程处理完请求后向用户发送返回消息。这种设计大概是这样:

 while(server is active){
 监听请求
 把请求转给工作线程
 }

这使得服务端线程可以很快的返回监听状态,于是更多的用户可以发请求给服务端,服务端的响应性更好了。

对于桌面应用也是一样的。如果你点击一个按钮开启一个长时间任务,并由更新界面的线程执行该任务。那么在该任务执行时,程序看上去是不响应的。也可以把任务交给工作线程,这样当工作线程忙于处理任务时,界面线程就有空闲来响应其他的用户请求。工作线程完成任务后通知界面线程,于是界面线程将任务结果更新到程序界面。采用工作线程设计的程序会显得有更好的响应性。

让CPU资源在不同任务间分配的更均衡

设想有一个接受客户端请求的服务端。假设其中一个客户端发送了一个请求,处理该请求耗时很长——比如要10秒。如果服务端只用一个线程来处理所有任务,那么直到该耗时请求被完全处理完,所有后面的请求都必须等待。

通过将CPU时间分配到多个线程,并且让CPU在线程之间切换的方式,多个线程便可以更公平的共享CPU执行时间。这样即使其中某个请求很耗时,其他的快速请求可以和这个耗时请求并行执行。当然,这会让耗时请求处理的更慢,因为该请求无法再独占CPU来处理自己的任务了。即便如此,其他请求等待的时间会更短,因为它们不用再等耗时任务完成就可以处理了。而假如只有一个耗时请求待处理,那该任务仍然可以独占CPU。

下一篇:
2020版java并发和多线程教程(三):多线程的代价

更多阅读:
系列专栏:java并发和多线程教程2020版