在使用多线程时,一定要知道一个道理:处理速度的最终决定因素是CPU、内存等,在单CPU(无论多少核)上,分配CPU资源的单位是“进程”而不是“线程”。
假设我要拷贝100万条数据,单CPU电脑,用一个进程,在单线程的情况下,CPU占用率为5%,耗时1000秒。那么当在这个进程下,开辟10个线程同时去运行,是不是CPU占用率增加到50%,耗时减少到100秒呢?显然不是。我实测出来的情况是这样的:
“CPU占用率仍然是5%,总耗时仍然是1000秒。且每个线程的运行时间也为1000秒。”
重点是后面那句,怎么理解?意味着什么?
我的理解如下:进程只有一个,所以分配的CPU资源是一定的,多线程只不过是轮流抢占CPU而已,并不会真正提高处理速度 。这意味着,多线程的作用主要在于提高了并发数量,比如http请求,如果是单线程,一次只能接收一个请求,多线程则可以同时接收多个请求。
但是多线程由于轮换使用CPU,会造成单个线程的执行速度变慢(以前CPU供一个线程使用,现在要供多个线程轮流使用了)。而且在时间片轮转的时候,频繁切换线程也会造成一定的时间浪费。但是在多CPU的服务器上,多线程就很有优势了,它不但能提高并发数量,而且能提高处理速度。因为在多CPU的服务器上,CPU调度很灵活,当一个线程占用着一个CPU的时候,其他线程可以被分配给其他CPU去处理,从而实现了“真正意义上地并行”。
什么是线程上下文切换
上下文切换的精确定义可以参考: http://www.linfo.org/context_switch.html。多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,为了让用户感觉这些任务正在同时进行,操作系统的设计者巧妙地利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能,但同时也带来了保存现场和加载现场的直接消耗。(Note. 更精确地说, 上下文切换会带来直接和间接两种因素影响程序性能的消耗. 直接消耗包括: CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉; 间接消耗指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作。
参考:http://www.linfo.org/context_switch.html
理论上多线程的一种用途就是能同时做好几件事情,以提高效率。但实际问题是,CPU的数量(核心数,下同)是有限的,而且并不多。如果你的CPU有8个CPU,并且整个系统中有8个线程的话,不考虑中断等因素,每个线程理论上能一直执行下去。然而多于8个线程以后,操作系统就必须进行调度,也就是分配时间片。具体的分配方案,或者说调度算法有很多种,详情参见Scheduling (computing)。如果一个进程创建了很多线程的话,最多也只有8个能够处于执行的状态[2],其余的线程必须等待调度。线程被调度的时候需要进行上下文切换,这个操作是一种额外的开销。线程数量过多的时候,上下文切换产生的额外开销会对系统的效率造成负面影响。
Windows概念体系下的内核级调度,被称作线程。线程和进程都是Windows的内核对象,线程是Windows内核调度的最小单位,进程是Windows内核中线程的容器。
内核级调度和用户级调度
总结:
第一,看硬件。**如果是在比较强大的、多CPU的服务器上运行程序,可以使用多线程来提高并发数和执行速度。**但是线程也不宜过多,即使是16个CPU的服务器,同一时间最多也只能真正意义上地并发处理16个线程,多出来的线程还是要等待。
第二,看用途。如果你不在乎处理速度,仅仅是为了提高并发处理能力,那么理所当然地用多线程,但是如果你仅仅是想提高处理速度,且又是在单CPU机器上运行,那么多线程并不值得。如果你的任务很耗时,且可以一部分、一部分地做,那么最好不要用多线程(好比搬砖,单线程一次搬10块,总共搬10天,但搬一块算一块,到第9天的时候,你就搬完90块砖了;如果你用10个线程同时去搬砖,同样要搬10天,但是到第9天的时候,这10个线程100块砖都“还在路上”,一块砖都没搬完!)。
单CPU上,多线程不会提高处理速度,只是提高并发能力,实际上表现为轮流执行。多线程可能导致上下文切换开销,反而降低效率。在多CPU服务器上,多线程能实现并行处理,提高执行速度和并发数量。选择多线程要考虑硬件和任务特性,避免线程过多导致的性能损失。
723





