创建多少线程合适?
我总结了
-
为什么要使用多线程?
因为程序执行多任务时,可以执行的更快,快 取决于 延迟 ,吞吐量 。降低延迟,提高吞吐量
-
多线程的应用场景有哪些?
io密集型:磁盘和内存打交道之类的操作
cpu密集型:复杂计算,几乎不操作磁盘,就是一个劲的计算 -
线程数如何创建?
cpu密集型 线程数和cpu核数相等就行(不过在工程上,线程的数量一般会设置为“CPU 核数 +1”),为什么呢?
-
假设只有cpu计算,io操作很少甚至没有的一个场景;
- 线程数如果少于cpu核数,如果计算量充足的情况下,每个线程都是正常工作,没有线程切换,但是存在cpu核没有利用到的情况,这是cpu利用率不足。
- 线程数如果多于CPU核数,如果计算量充足的情况下,那么多出来的线程会争抢CPU资源,造成线程切换的成本。
-
io密集型 最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)] (工程上,一般是cpu核数*2+1
针对io密集型和cpu密集型的合适线程数实践
本机配置
实验代码
/**
* @author ggBall
* @version 1.0.0
* @ClassName IoTest.java
* @Description TODO
* @createTime 2022年07月06日 14:48:00
*/
public class PerformanceTest {
String classFilePath = PerformanceTest.class.getResource("").getPath();
CountDownLatch latch;
/**
* 按照公式 io密集型 线程数=核数*2+1 本机8核 线程数应该是17
* 结果 20次的10000次io操作 (8核*2+1)10个线程操作 平均1705.3ms
* 结果 20次的10000次io操作 (8核*2+1)11个线程操作 平均1605.35ms
* 结果 20次的10000次io操作 (8核*2+1)12个线程操作 平均1595.45ms
* 结果 20次的10000次io操作 (8核*2+1)13个线程操作 平均1625.1ms
* 结果 20次的10000次io操作 (8核*2+1)14个线程操作 平均1648.65ms
* 结果 20次的10000次io操作 (8核*2+1)15个线程操作 平均1654.95ms
* 结果 20次的10000次io操作 (8核*2+1)16个线程操作 平均1655.3ms
* 结果 20次的10000次io操作 (8核*2+1)17个线程操作 平均1645.5ms
* 结果 20次的10000次io操作 (8核*2+1)18个线程操作 平均1645.6ms
* 结果 20次的10000次io操作 (8核*2+1)19个线程操作 平均1661.35ms
* 结果 20次的10000次io操作 (8核*2+1)20个线程操作 平均1675.95ms
* @throws InterruptedException
*/
@Test
public void ioTest() throws InterruptedException {
int ioTimes = 10000;
int threadNum;
for (int i1 = 10; i1 <= 20; i1++) {
threadNum = i1;
ArrayList<Long> times = new ArrayList<>();
for (int i = 0; i < 20; i++) {
long time = singleIo(ioTimes, threadNum);
times.add(time);
}
Double avgTime = times.stream().collect(Collectors.averagingLong(item -> item));
// System.out.println(times);
System.out.println("结果 20次的"+ioTimes+"次io操作 (8核*2+1)"+threadNum+"个线程操作 平均"+avgTime+"ms");
}
}
/**
* 按照公式 cpu密集型 线程数=核数 本机8核 线程数应该是8
* 结果 20次的10000次cpu操作 (8核*2+1)15个线程操作 平均2.0ms
* 结果 20次的10000次cpu操作 (8核*2+1)14个线程操作 平均1.85ms
* 结果 20次的10000次cpu操作 (8核*2+1)13个线程操作 平均2.0ms
* 结果 20次的10000次cpu操作 (8核*2+1)12个线程操作 平均1.6ms
* 结果 20次的10000次cpu操作 (8核*2+1)11个线程操作 平均1.5ms
* 结果 20次的10000次cpu操作 (8核*2+1)10个线程操作 平均1.6ms
* 结果 20次的10000次cpu操作 (8核*2+1)9个线程操作 平均1.55ms
* 结果 20次的10000次cpu操作 (8核*2+1)8个线程操作 平均1.35ms
* 结果 20次的10000次cpu操作 (8核*2+1)7个线程操作 平均1.4ms
* 结果 20次的10000次cpu操作 (8核*2+1)6个线程操作 平均1.6ms
* 结果 20次的10000次cpu操作 (8核*2+1)5个线程操作 平均1.45ms
* 结果 20次的10000次cpu操作 (8核*2+1)4个线程操作 平均1.4ms
* 结果 20次的10000次cpu操作 (8核*2+1)3个线程操作 平均1.0ms
* 结果 20次的10000次cpu操作 (8核*2+1)2个线程操作 平均1.7ms
* @throws InterruptedException
*/
@Test
public void cpuTest() throws InterruptedException {
int cpuTimes = 10000;
int threadNum;
for (int i1 = 16; i1 > 1; i1--) {
threadNum = i1;
ArrayList<Long> times = new ArrayList<>();
for (int i = 0; i < 20; i++) {
long time = singleCPU(cpuTimes, threadNum);
times.add(time);
}
Double avgTime = times.stream().collect(Collectors.averagingLong(item -> item));
// System.out.println(times);
System.out.println("结果 20次的"+cpuTimes+"次cpu操作 (8核*2+1)"+threadNum+"个线程操作 平均"+avgTime+"ms");
}
}
/**
* 执行单次多线程cpu任务
* @param cpuTimes
* @param threadNum
* @return
* @throws InterruptedException
*/
private long singleCPU(int cpuTimes, int threadNum) throws InterruptedException {
latch = new CountDownLatch(cpuTimes);
long start = System.currentTimeMillis();
// 170次io操作 (8核*2+1)17个线程操作
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
for (int i = 0; i < cpuTimes; i++) {
CPUTasker tasker = new CPUTasker();
executorService.submit(tasker);
}
latch.await();
long end = System.currentTimeMillis();
// System.out.println(end-start+"ms");
return end-start;
}
/**
* 执行单次多线程io任务
* @param ioTimes
* @param threadNum
* @return
* @throws InterruptedException
*/
public long singleIo(int ioTimes,int threadNum) throws InterruptedException {
latch = new CountDownLatch(ioTimes);
long start = System.currentTimeMillis();
// 170次io操作 (8核*2+1)17个线程操作
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
for (int i = 0; i < ioTimes; i++) {
IoTasker tasker = new IoTasker();
executorService.submit(tasker);
}
latch.await();
long end = System.currentTimeMillis();
// System.out.println(end-start+"ms");
return end-start;
}
/**
* io任务
*/
class IoTasker implements Runnable{
@Override
public void run() {
String fileName = "text.txt";
FIleUtils fIleUtils = new FIleUtils();
fIleUtils.writeFile(classFilePath+ "/" + fileName,"sda实打实大sss11212");
try {
fIleUtils.fileRead(classFilePath+ "/" + fileName);
// System.out.println(Thread.currentThread().getName()+"io完成");
latch.countDown();
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}
/**
* cpu任务
*/
class CPUTasker implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1024; i++) {
for (int j = 0; j < 1024; j++) {
for (int k = 0; k < 1024; k++) {
}
}
}
latch.countDown();
}
}
}
实践总结
从实验代码看,io密集型设置的线程数 确实符合 线程数=核数*2+1
公式,测试结果显示17个线程耗时最少;但是cpu密集型 从线程数3~12个 耗时都在1.5ms左右,与公式线程数=核数
相差很大,
所以设置线程的公式只是参考标准,生产环境还得实际测试。
问题
实践总结
从实验代码看,io密集型设置的线程数 确实符合 线程数=核数*2+1
公式,测试结果显示17个线程耗时最少;但是cpu密集型 从线程数3~12个 耗时都在1.5ms左右,与公式线程数=核数
相差很大,
所以设置线程的公式只是参考标准,生产环境还得实际测试。
问题
I/O 耗时 和 CPU 耗时 不知道使用什么工具测试?