CPU-bound(计算密集型) 与 I/O-bound(I/O密集型)

前言

在一个技术交流群里面看到有人在问,如何设置应用的线程池大小?有人回复了说,不谈并发类型(计算密集型或者IO密集型)的话,这个问题纯属瞎扯淡。下面是一些个人理解与在网上看到的比较好的解释。

1、CPU-bound(计算密集型)

计算密集型是说需要这个应用的运行需要充分运用CPU资源,比如说Hadoop离线处理应用、Storm清洗项目、视频图片渲染等等,CPU运算资源都被用来进行逻辑计算,这里引发线程池问题其实是多核CPU的问世所引发的,因为CPU上下文切换多了不好,少了也不好,不多不少的那个值刚刚好,所以计算密集型的应用完全是靠CPU核数来工作并影响性能的,看到有一个方案比较好:

线程数 = CPU核数+1

//也可以设置成CPU核数*2,这还是要看JDK的使用版本,以及CPU配置(服务器的CPU有超线程)。对于JDK1.8来说,里面增加了一个并行计算,计算密集型的较理想线程数 = CPU内核线程数*2

有人写过一个例子来验证:

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 计算文件夹大小
 */
public class FileSizeCalc {

    static class SubDirsAndSize {
        public final long size;
        public final List<File> subDirs;

        public SubDirsAndSize(long size, List<File> subDirs) {
            this.size = size;
            this.subDirs = Collections.unmodifiableList(subDirs);
        }
    }

    private SubDirsAndSize getSubDirsAndSize(File file) {
        long total = 0;
        List<File> subDirs = new ArrayList<File>();
        if (file.isDirectory()) {
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    if (child.isFile())
                        total += child.length();
                    else
                        subDirs.add(child);
                }
            }
        }
        return new SubDirsAndSize(total, subDirs);
    }

    private long getFileSize(File file) throws Exception{
        final int cpuCore = Runtime.getRuntime().availableProcessors();
        final int poolSize = cpuCore+1; //修改这里
        ExecutorService service = Executors.newFixedThreadPool(poolSize);
        long total = 0;
        List<File> directories = new ArrayList<File>();
        directories.add(file);
        SubDirsAndSize subDirsAndSize = null;
        try{
            while(!directories.isEmpty()){
                List<Future<SubDirsAndSize>> partialResults= new ArrayList<Future<SubDirsAndSize>>();
                for(final File directory : directories){
                    partialResults.add(service.submit(new Callable<SubDirsAndSize>(){
                        @Override
                        public SubDirsAndSize call() throws Exception {
                            return getSubDirsAndSize(directory);
                        }
                    }));
                }
                directories.clear();
                for(Future<SubDirsAndSize> partialResultFuture : partialResults){
                    subDirsAndSize = partialResultFuture.get(100,TimeUnit.SECONDS);
                    total += subDirsAndSize.size;
                    directories.addAll(subDirsAndSize.subDirs);
                }
            }
            return total;
        } finally {
            service.shutdown();
        }
    }

    public static void main(String[] args) throws Exception {
        for(int i=0;i<10;i++){
            final long start = System.currentTimeMillis();
            long total = new FileSizeCalc().getFileSize(new File("e:/m2"));
            final long end = System.currentTimeMillis();
            System.out.format("文件夹大小: %dMB%n" , total/(1024*1024));
            System.out.format("所用时间: %.3fs%n" , (end - start)/1.0e3);
        }
    }
}

需要做的就是把程序里的线程池大小进行修改,观察消耗的时间。

2、I/O bound(I/O密集型)

比如说一个后台管理系统,我们后端的接口都是用来做CURD的,这个时候就会涉及到网络传输,IO交互,造成的结果就是IO等待,线程等待。因为线程上下文切换也是有代价的,这个时候面临的问题就是如何设置线程池的合理大小,使得CPU尽量不处于空闲状态(线程等待是不占CPU的),尽量提高CPU利用率。下面是一个对于IO密集型应用线程池设置的公式:

线程数 = CPU核心数/(1-阻塞系数)

//这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整。

线程池从根本上解决不了IO密集型应用的问题的,需要使用异步非阻塞的方式,如何让等待线程异步释放才是解决的关键,关于这一点,Netty做得很好。

猜你喜欢

转载自blog.csdn.net/rickiyeat/article/details/80256642
今日推荐