多线程(二)使用多线程的准备知识

一、为什么要使用多线程?
        【使计算机所有资源在执行任务的时候能够全部利用上,以提升计算机资源利用率的方式来提升系统执行效率】
  CPU的单核运行速度由于硬件技术问题已经遇到瓶颈,而概念性的“光脑”貌似离我们还很遥远,现在的计算机性能提升方向是向多核发展。多核同时工作,协同完成任务。大家熟知的神威·太湖之光超级计算机总共使用了40960颗处理器,总计拥有10649600颗核心、1.31PB内存。即使是目前市场上的主流家用电脑也一般达到四核心八线程的配置标准。那么对于这些多核的CPU,在开发软件的时候就不得不考虑如何充分利用上每一个核的性能,以至于使系统运行的效率更高。

二、CPU是执行的是线程任务还是进程任务?
        【CPU执行的是线程中定义的任务】
        进程是对内存资源的抽象,线程是对执行任务的抽象。CPU执行的是线程任务,和进程没有任何关系。所以,CPU中的核在任意时间点只能执行某一个线程的任务,具体执行哪个线程就要看操作系统的任务调度策略。在单核多线程任务中,操作系统会把CPU资源按照时间片划分,根据线程的优先级选择线程进行执行任务。

三、程序中线程的数量控制在核数的1~2倍对吗?
        【不对!】

         左图为工作时win10 四核八线程 线程数: 2094。右图为闲置centos7  单核 线程数 :127 。线程数量远远超过CPU核心数量的上百倍。

 
        所以在开发程序过程中,如果不是线程开启机制错误,就不会产生成上万级别的线程导致线程切换浪费资源,而几百个线程切换对CPU来说还不至于产生过多的资源消耗。
             线程开启机制错误例子:socket每接收一个请求便创建一个线程执行任务。这种情况下,很容易的便能开启上千上万个线程(在工作中遇到过)。
四、线程频繁切换会耗费资源?
        【消耗资源肯定会,但是消耗的资源一般情况下没必要重视,当需要去重视的时候你就不会在看本文了】
        线程消耗必定会耗费计算机资源,影响系统的执行效率。但是这种级别的资源浪费还没必要引起关注去考虑优化,随便优化一个SQL查询就远远比优化线程切换性能提升的多。
        
    测试:
        计算任务:计算1亿次 f(i)=(i * 10000.56) / 200 (i从1循环到10000)  每组数据测试4次。
        电脑配置: 四核八核心CPU 8G内存 win10系统(测试过程中运行的有其它程序,但整个过程中没有新的应用程序开启或者关闭。测试前后计算机进程数102左右,线程数210左右)
        数据如下:单位s
                1线程执行10000 * 10000次相同任务运算时间:0.043584183000000006
                1线程执行10000 * 10000次相同任务运算时间:0.03411422
                1线程执行10000 * 10000次相同任务运算时间:0.033755275
                1线程执行10000 * 10000次相同任务运算时间:0.037857329
                
                10线程执行10000 * 1000次相同任务运算时间:0.020971566
                10线程执行10000 * 1000次相同任务运算时间:0.020400791
                10线程执行10000 * 1000次相同任务运算时间:0.043275417000000004
                10线程执行10000 * 1000次相同任务运算时间:0.020918537
               
                50线程执行10000 * 200次相同任务运算时间:0.041526313
                50线程执行10000 * 200次相同任务运算时间:0.028923265
                50线程执行10000 * 200次相同任务运算时间:0.020633701000000003
                50线程执行10000 * 200次相同任务运算时间:0.01057820000000002

                
                100线程执行10000 * 100次相同任务运算时间:0.034126764000000004
                100线程执行10000 * 100次相同任务运算时间:0.036614853
                100线程执行10000 * 100次相同任务运算时间:0.060594536000000004
                100线程执行10000 * 100次相同任务运算时间:0.046860865
                
                500线程执行10000 * 20次相同任务运算时间:0.054617363
                500线程执行10000 * 20次相同任务运算时间:0.090330437
                500线程执行10000 * 20次相同任务运算时间:0.060385270000000005
                500线程执行10000 * 20次相同任务运算时间:0.06421362700000001
                
                1000线程执行10000 * 10次相同任务运算时间:0.101977442
                1000线程执行10000 * 10次相同任务运算时间:0.09040313700000001
                1000线程执行10000 * 10次相同任务运算时间:0.094736125
                1000线程执行10000 * 10次相同任务运算时间:0.09127384000000001
                
                2000线程执行10000 * 5次相同任务运算时间:0.16429143000000002
                2000线程执行10000 * 5次相同任务运算时间:0.18807325200000002
                2000线程执行10000 * 5次相同任务运算时间:0.16652977000000002
                2000线程执行10000 * 5次相同任务运算时间:0.18108197
                
                
                5000线程执行10000 * 2次相同任务运算时间:0.44204793600000003
                5000线程执行10000 * 2次相同任务运算时间:0.484968116
                5000线程执行10000 * 2次相同任务运算时间:0.405066727
                5000线程执行10000 * 2次相同任务运算时间:0.42432967400000005
                
                
                10000线程执行10000 * 1次相同任务运算时间:1.453686674
                10000线程执行10000 * 1次相同任务运算时间:1.307516071
                10000线程执行10000 * 1次相同任务运算时间:1.5121169970000001
                10000线程执行10000 * 1次相同任务运算时间:1.332141795
                

由数据中可以看出,在程序开启1-50个线程时,执行时间在下降,10-1000个线程之内,执行时间都在同一个量级,超过一千线程之后,时间提升一个量级。因此,粗略来看 1000个以下线程切换并不会浪费大量系统资源。在程序中如果线程开启机制正确,同时使用到线程池,那么系统优化瓶颈就不会在线程切换的问题上。没必要在这个角度考虑系统优化的问题。
测试代码如下:

 //scala2.12.12 jdk1.8
  def showNCPUTime(): Unit = {
   //测试过程中调整threadNum,inner 这两个参数,同时保证两者之积不变 inner * theradNum =10000
  val threadNum
= 2000 val inner = 5 //固定不变 val top = 10000 val state = new CountDownLatch(threadNum) var threads = new Array[Thread](threadNum) for (time <- (0 until threadNum)) { val thread = new Thread(new Runnable { override def run(): Unit = { for (i <- (1 to top)) { for (j <- (1 to inner)) { //i 每次都是从1 到 1000000 val temp = (i * 105300.56) / 200 } } state.countDown() } }) threads(time) = thread } var startTime = System.nanoTime for (time <- (0 until threadNum)) { threads(time).start() } state.await() val endTime = System.nanoTime println(threadNum + "线程执行" + top + " * " + inner + "次相同任务运算时间:" + (endTime - startTime) * 0.000000001) }

 
五、使用多线程的难点在哪里?
         多线程的难点在于共享数据读写顺序的问题,保证多个线程对同一数据的操作不会产生混乱。
        程序中流动的都是数据,程序影响的也都是数据。在多个线程同时访问共享数据的时候,由于线程读取/写入的时机不对而导致数据出错,进而影响业务。

下面的问题涉及到操作系统底层知识,列出来只是为了引起读者的思考,不再回答,以免误人误己。
六、对于多核CPU,线程任务是如何分配到每个核上面的?
七、单核CPU任务调度策略。在多个线程中,操作系统是如何选取其中某个线程分配给CPU执行的?选取规则是什么?
八、线程切换耗费资源,那么都耗费在什么地方?挂起一个线程的时候操作系统都进行了哪些操作?
九、操作系统开启一个新线程需要执行哪些步骤?计算机能够开启的最大线程数是多少?JVM能够开启的最大线程数是多少?他们和内存的大小有什么关系?

=============================================

原文链接:多线程(二)使用多线程的准备知识 转载请注明出处!

=============================================

-----end

原文地址:https://www.cnblogs.com/PerkinsZhu/p/7345862.html