多线程就一定能提高处理速度吗?

文章探讨了多线程如何提高CPU利用率,尤其是在IO等待时,以及上下文切换对处理速度的影响。指出计算密集型任务中过多线程可能导致性能下降,还分析了上下文切换的常见原因。
摘要由CSDN通过智能技术生成

每个程序员都知道,多线程能提高应用吞吐量和处理速度。但不是每个程序员都知道为什么?

CPU运行时,通过将于运行时间分片,通过调度来分配给各个进程线程来执行。因为时间片非常短,所以常常让人误以为是多个线程是同时并行执行。

使用多线程来提高程序处理速度,其本质是提高对CPU的利用率。主要是两个方面

  • 柱塞等待时充分利用CPU
    当程序发生阻塞的操作时候,例如IO等待,CPU将就空闲下来了。而使用多线程,当一些线程发生阻塞的时候,另一些线程则仍能利用CPU,而不至于让CPU一直空闲。
  • 利用CPU的多核并行计算能力
    现在的CPU基本上都是多核的。使用多线程,可以利用多核同时执行多个线程,而不至于单线程时一个核心满载,而其他核心空闲。

多线程就一定能提高处理速度吗?显示着不一定。当程序偏计算型的时候,盲目启动大量线程来并发,并不能提高处理速度,反而会降低处理速度。因为在多个线程进行切换执行的时候会带会一定的开销。其中有 上下文切换开销,CPU调度线程的开销,线程创建和消亡的开销等。其中主要是上下文切换带来的开销。

上下文切换的开销,主要是来自于当线程切换时保存上一个线程现场和载入下一个线程现场的操作。

使用空循环来模拟计算性任务,看下在不同数量的线程,程序的表现

import java.util.ArrayList;
import java.util.List;
 
public class CounterDemo {
 
 
    private static final long num = 1000000000L;
 
    public static void splitCount(int threadNum) {
        for (long i = 0; i < num/threadNum; i++) {}
    }
 
    public static long getIntervalTimeToNow(long startTime) {
        return System.currentTimeMillis() - startTime;
    }
 
    public static void main(String[] args) throws InterruptedException {
        countWithMultithread(1);
        countWithMultithread(10);
        countWithMultithread(100);
        countWithMultithread(1000);
        countWithMultithread(10000);
    }
 
    private static void countWithMultithread(final int threadNum) throws InterruptedException {
        long startTime;
        Runnable splitCount = new Runnable() {
            @Override
            public void run() {
                CounterDemo.splitCount(threadNum);
            }
        };
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < threadNum; i++) {
            Thread thread1 = new Thread(splitCount);
            list.add(thread1);
        }
        startTime = System.currentTimeMillis();
        for (Thread th: list) {
            th.start();
        }
        for (Thread th: list) {
            th.join();
        }
        System.out.println(String.format("%1$9d", threadNum) + " thread need:"+String.format("%1$6d",getIntervalTimeToNow(startTime)));
    }
 
 
}

输出结果

        1 thread need:   409
       10 thread need:    92
      100 thread need:   140
     1000 thread need:   226
    10000 thread need:   978
   100000 thread need: 10059

执行的时候,使用vmstat 查看 CS (context switch)切换次数

vmstat 1
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 2701584 151472 9676072    0    0     0    60  393  419  0  0 100  0  0
0  0      0 2701708 151472 9676084    0    0     0     0  751  751  1  0 99  0  0
0  0      0 2701708 151472 9676092    0    0     0     0  354  384  0  0 100  0  0
0  0      0 2701708 151472 9676096    0    0     0     0  435  464  0  0 100  0  0
0  0      0 2701708 151472 9676108    0    0     0    72  439  491  0  0 100  0  0
2  0      0 2701708 151472 9676128    0    0     0    32  381  453  0  0 100  0  0
1  0      0 2680344 151472 9676168    0    0     0     4 6077 4869 21  1 78  0  0
4  0      0 2664740 151472 9676180    0    0     0     0 126656 149528 17 11 72  0  0
3  0      0 2564376 151472 9676188    0    0     0     0 107107 138418 12 11 77  0  0
3  0      0 2565556 151472 9676196    0    0     0     0 128143 166234  7 14 79  0  0
4  0      0 2563056 151472 9676204    0    0     0    64 125162 163707  7 13 79  0  0
3  0      0 2567808 151472 9676208    0    0     0     0 136266 180092  7 13 80  0  0
2  0      0 2566560 151472 9676216    0    0     0     0 117768 154666  7 14 79  0  0
1  0      0 2568276 151472 9676220    0    0     0     0 107585 139240  8 13 79  0  0

当上下文切换最多的时候每秒切换了18W+次。从输入结果来看,线程1K,10K的时候,多线程并没有打来处理效率的提升,反而下降了。

引起上下文切换的原因有哪些?主要有以下几种:

  • 当前任务的时间片用完之后,系统CPU正常调度下一个任务;
  • 当前任务碰到IO阻塞,调度线程将挂起此任务,继续下一个任务;
  • 多个任务抢占锁资源,当前任务没有抢到,被调度器挂起,继续下一个任务;
  • 用户代码挂起当前任务,让出CPU时间;
  • 硬件中断;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值