当有很多个文件需要进行处理的时候,我们为了提高程序执行的性能,往往想当然的开多个线程并行执行文件的读/写动作。但是其实这种“想当然”是错误的,下面我们就来看看,对于磁盘IO密集型的应用,多线程到底带来了什么?
首先,我写了一段读文件的程序,这个程序支持用单线程/多线程两种方式读入多个文件,并且记录整个读文件的耗时,最后来比较一下单线程/多线程两种模型在读文件上的性能差别:
- public class TestMultiThreadIO {
- /**
- * @param args
- * @throws IOException
- */
- public static void main(String[] args) throws Exception {
- if (args.length != 2) {
- throw new IllegalArgumentException("Usage: isSingle[true|false] filenames(split with ,)");
- }
- long startTime = System.currentTimeMillis();
- boolean isSingle = Boolean.parseBoolean(args[0]);
- String[] filenames = args[1].split(",");
- // 主线程先打开这组文件,为了排除掉单线程与多线程打开文件的性能差异,关注点是读文件的过程
- InputStream[] inputFiles = new InputStream[filenames.length];
- for (int i = 0; i < inputFiles.length; i++) {
- inputFiles[i] = new BufferedInputStream(new FileInputStream(filenames[i]));
- }
- if (isSingle) {
- System.out.println("single thread cost: " + singleThread(inputFiles) + " ms");
- } else {
- System.out.println("multi thread cost: " + multiThread(inputFiles) + " ms");
- }
- for (int i = 0; i < inputFiles.length; i++) {
- inputFiles[i].close();
- }
- System.out.println("finished, total cost: " + (System.currentTimeMillis() - startTime));
- }
- private static long singleThread(InputStream[] inputFiles) throws IOException {
- long start = System.currentTimeMillis();
- for (InputStream in : inputFiles) {
- while (in.read() != -1) {
- }
- }
- return System.currentTimeMillis() - start;
- }
- private static long multiThread(final InputStream[] inputFiles) throws Exception {
- int threadCount = inputFiles.length;
- final CyclicBarrier barrier = new CyclicBarrier(threadCount + 1);
- final CountDownLatch latch = new CountDownLatch(threadCount);
- for (final InputStream in : inputFiles) {
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- barrier.await();
- while (in.read() != -1) {
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- latch.countDown();
- }
- }
- });
- t.start();
- }
- long start = System.currentTimeMillis();
- barrier.await();
- latch.await();
- return (System.currentTimeMillis() - start);
- }
- }
程序写好了,下面介绍一下我的测试环境:
CPU: 24核(Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz)
内存:32GB
系统:64位 CentOS release 5.8
在测试之前,需要说明一下,Linux系统为了提高IO性能,对于文件的读写会由操作系统缓存起来,这就是cached的作用:
- $ free -m
- total used free shared buffers cached
- Mem: 32144 818 31325 0 0 8
- -/+ buffers/cache: 809 31334
- Swap: 4096 38 4057
这里我先准备好了一个文件,文件名叫“0”,我们下面尝试读入这个文件:
- $ dd if=0 of=/dev/null bs=1024b count=100
再用free -m看,可以看到cached空间增长了50MB:
- $ free -m
- total used free shared buffers cached
- Mem: 32144 868 31276 0 0 58
- -/+ buffers/cache: 808 31335
- Swap: 4096 38 4057
所以,我们在测试时,为了排除系统缓存对测试的影响,应该在每次测试完成后都主动将系统缓存清空:
- sync && echo 3 > /proc/sys/vm/drop_caches && echo 0 > /proc/sys/vm/drop_caches
清空后可以看到cached确实还原了:
- $ free -m
- total used free shared buffers cached
- Mem: 32144 818 31326 0 0 8
- -/+ buffers/cache: 809 31334
- Swap: 4096 38 4057
具体有关cached/buffers的信息有兴趣的同学可以google一下。
下面开始正式测试
测试用例1:
先生成一批文件,为了方便,文件名都按照0、1、2...的序号来命名,每个文件内容不同(用dd+urandom生成),大小相同都是50MB的文件。
然后开始进行单线程读取10个文件的测试:
- $ java TestMultiThreadIO true 0,1,2,3,4,5,6,7,8,9 && sync && echo 3 > /proc/sys/vm/drop_caches && echo 0 > /proc/sys/vm/drop_caches
- single thread cost: 20312 ms
- finished, total cost: 20322
下面是测试进行过程中,系统的各项资源开销:
- ----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
- usr sys idl wai hiq siq| read writ| recv send| in out | int csw
- 0 0 98 2 0 0| 17M 0 | 384B 412B| 0 0 |1139 374
- 2 0 96 2 0 0| 38M 0 | 686B 522B| 0 0 |1502 1083
- 4 0 96 0 0 0| 51M 0 | 448B 412B| 0 0 |1223 194
- 4 0 96 0 0 0| 51M 0 | 448B 412B| 0 0 |1220 186
- 4 0 95 0 0 0| 50M 0 | 812B 412B| 0 0 |1215 235
- 4 0 96 0 0 0| 48M 240k| 448B 412B| 0 0 |1218 286
- 4 0 96 0 0 0| 49M 24k| 512B 412B| 0 0 |1213 213
- 4 0 96 0 0 0| 50M 0 | 622B 476B| 0 0 |1218 200
- 4 0 96 0 0 0| 51M 0 | 384B 412B| 0 0 |1215 188
- 4 0 96 0 0 0| 51M 0 | 448B 412B| 0 0 |1219 174
- 4 0 94 2 0 0| 44M 0 | 448B 412B| 0 0 |1194 339
- 4 0 94 2 0 0| 49M 24k| 862B 412B| 0 0 |1219 366
- 4 0 96 0 0 0| 51M 0 | 384B 886B| 0 0 |1215 194
- 4 0 96 0 0 0| 50M 0 | 512B 1112B| 0 0 |1218 194
- 4 0 96 0 0 0| 51M 0 | 448B 412B| 0 0 |1216 184
- 4 0 95 1 0 0| 49M 48k| 558B 540B| 0 0 |1216 298
- 4 0 96 0 0 0| 50M 24k| 384B 412B| 0 0 |1215 272
- 4 0 96 0 0 0| 50M 48k| 448B 412B| 0 0 |1220 236
- 4 0 96 0 0 0| 50M 168k| 448B 412B| 0 0 |1225 187
- 4 0 96 0 0 0| 51M 0 | 448B 476B| 0 0 |1217 178
- 4 0 96 1 0 0| 44M 0 | 384B 412B| 0 0 |1187 180
- 3 0 96 1 0 0| 38M 240k| 448B 412B| 0 0 |1208 315
小结:程序总共耗时20s,由于是单线程读,所以cpu开销非常小,usr在4%,基本没有iowait。上下文切换开销在200~300左右。
测试用例2:
同样是这10个文件,用多线程读取(每个文件一个线程):
- $ java TestMultiThreadIO false 0,1,2,3,4,5,6,7,8,9 && sync && echo 3 > /proc/sys/vm/drop_caches && echo 0 > /proc/sys/vm/drop_caches
- multi thread cost: 19124 ms
- finished, total cost: 19144
下面是测试进行过程中,系统的各项资源开销:
- ----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
- usr sys idl wai hiq siq| read writ| recv send| in out | int csw
- 1 0 95 4 0 0| 29M 0 | 512B 412B| 0 0 |1515 1402
- 5 0 75 20 0 0| 54M 0 | 622B 428B| 0 0 |1253 652
- 4 0 74 21 0 0| 56M 0 | 320B 318B| 0 0 |1245 601
- 5 0 76 20 0 0| 56M 0 | 448B 318B| 0 0 |1242 602
- 4 0 84 12 0 0| 52M 0 | 622B 476B| 0 0 |1228 600
- 4 0 85 12 0 0| 45M 264k| 384B 412B| 0 0 |1201 539
- 4 0 83 12 0 0| 53M 0 | 798B 886B| 0 0 |1225 588
- 4 0 81 15 0 0| 54M 0 | 384B 412B| 0 0 |1234 596
- 4 0 86 10 0 0| 52M 16k| 384B 412B| 0 0 |1233 597
- 4 0 86 9 0 0| 53M 0 | 448B 412B| 0 0 |1237 582
- 4 0 80 16 0 0| 53M 0 | 896B 412B| 0 0 |1227 593
- 4 0 85 10 0 0| 52M 88k| 448B 412B| 0 0 |1238 607
- 4 0 83 13 0 0| 54M 0 | 384B 412B| 0 0 |1236 607
- 4 0 75 20 0 0| 55M 0 | 384B 412B| 0 0 |1238 587
- 4 0 80 15 0 0| 54M 0 | 448B 412B| 0 0 |1236 588
- 4 0 83 13 0 0| 46M 0 | 558B 476B| 0 0 |1200 528
- 4 0 77 19 0 0| 51M 16k| 448B 412B| 0 0 |1227 605
- 4 0 82 14 0 0| 52M 8192B| 448B 412B| 0 0 |1227 571
- 4 0 88 7 0 0| 56M 0 | 448B 412B| 0 0 |1245 615
- 4 0 94 2 0 0| 54M 0 | 384B 412B| 0 0 |1231 480
- 0 0 99 1 0 0|1056k 584k| 512B 884B| 0 0 |1092 235
由于wai比较高,所以再来看一下iostat的状况(iostat命令详解参考这里):
- iostat -x 1
- Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
- sda 103.00 2.00 228.00 0.00 54088.00 0.00 237.23 9.28 62.40 4.39 100.10
- sda1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
- sda2 0.00 2.00 0.00 0.00 0.00 0.00 0.00 0.10 0.00 0.00 9.50
- sda3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
- sda4 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
- sda5 103.00 0.00 228.00 0.00 54088.00 0.00 237.23 9.18 62.40 4.39 100.10
- avg-cpu: %user %nice %system %iowait %steal %idle
- 3.67 0.00 0.00 5.29 0.00 91.05
小结:程序总耗时19s,usr占用依然在4%(由于我们的测试程序基本没有什么计算工作,只是简单的读文件)。但通过iostat看到util已经到达100%,每次IO等待时间达到62ms,上下文开销也增长到500~600。
可以看到,1个线程增加到10个线程,执行时间仅仅降低了1s,但系统开销大了很多,主要是阻塞在IO操作上。
如果再加大线程数会发生什么呢?下面是用10/20/50/100个线程测试的结果:
总结:可以看出,当测试文件增多时,在单线程情况下,性能没有降低。但多线程情况下,性能降低的很明显,由于IO阻塞导致CPU基本被吃满。所以在实际编码过程中,如果遇到文件读写操作,最好用一个单独的线程做,其它线程可以分配给计算/网络IO等其它地方。而且要注意多个进程之间的文件IO的影响,如果多个进程分别做顺序IO,其实全局来看(如果是一块磁盘),就变成了随机IO,也会影响系统性能。
http://blueswind8306.iteye.com/blog/1983914
相关推荐
C++的多线程编程是提高程序效率的关键技术之一,特别是在需要高吞吐量、并发性和实时性的场景中。在C++中实现多线程,我们可以利用标准库中的`<thread>`头文件以及相关的同步机制,如互斥锁、条件变量等。 1. **...
在本项目实践中,我们将深入探讨Java中的多线程与高并发技术,特别是在人工智能领域的应用。Java作为一种广泛应用的编程语言,其强大的并发处理能力是其在大规模数据处理和高性能计算场景中备受青睐的原因之一。本...
它使用完成端口来管理多个线程处理多个套接字的I/O操作,极大地提高了系统的并发处理能力。 #### 基本编程实践例子 以下是一个简单的客户端代码示例,用于展示如何建立连接、发送数据及接收响应: ```c #include ...
本主题将深入探讨Python3中四种不同的并发模型:同步依序下载、多进程(multiprocessing)、多线程(multithreading)以及asyncio异步编程,并通过理论分析和实际案例对比它们之间的效率差异。 1. 同步依序下载: ...
1. **I/O完成端口(IOCP)**:IOCP是Windows提供的一种多线程并发处理I/O请求的方法。它允许多个线程共享一个完成端口,当I/O操作完成后,系统会将完成信息放入队列,由线程从队列中取出并处理。这样,即使在处理...
Java并发编程和多线程是两个紧密相关的概念,但它们之间存在着重要的区别。并发编程是一种高级的编程范式,旨在设计能够处理多个任务并行执行的程序,从而提高系统效率和资源利用率。它涵盖了一系列的技术和策略,如...
NIO的Selector组件使得单线程可以监视多个通道的状态变化,从而实现多路复用,这对于服务器端处理大量并发连接非常有用。选择器(Selector)通过注册Channel并监听其感兴趣的事件(如连接就绪、数据可读、写操作完成...
通过理论讲解与实战演练相结合的方式,深入剖析了多线程编程的核心原理及其在实际项目中的应用策略。 #### 第一阶段:基础理论与实践入门 ##### 1. 高并发编程概述 - **定义**:高并发编程是指系统能够同时处理大量...
本课程设计的主题是“多线程Web服务器”,这是一项深入理解网络编程、并发处理和服务器性能优化的重要实践。多线程技术在Web服务器中的应用能够显著提高服务器处理请求的能力,尤其是在高并发场景下。 首先,我们要...
本资源包“java基础(多线程,IO,集合,网络编程,泛型)”提供了对Java核心技术的全面介绍,包括五个核心主题:多线程、输入/输出(IO)、集合框架、网络编程和泛型。以下是对这些主题的详细讲解: 1. **多线程**: - ...
从简单的单线程模型到复杂的主从多线程模型,Netty提供了灵活多样的解决方案来应对各种应用场景下的并发挑战。接下来,文档将继续深入讨论具体的案例分析,帮助读者更全面地掌握Netty并发编程的相关知识。
- **多IO线程**: 通过使用多个IO线程来接收和处理网络请求,以提高系统并发能力和响应速度。 - **工作线程池**: 接收的请求会被推送到请求队列中,再由工作线程池中的线程处理,从而有效利用多核处理器资源。 - **...
Java多线程与并发编程是Java语言中用于处理多任务执行的关键技术,它能够帮助开发者设计出能够有效应对高并发请求的应用程序。在现代的线上(Online)和离线(Offline)应用中,合理利用多线程技术可以大幅提高系统...
输入/输出处理数据的传输,而多线程则涉及程序的并发执行。 首先,让我们深入理解Java的IO系统。Java.IO包包含了大量用于处理输入和输出操作的类和接口。输入流(InputStream)和输出流(OutputStream)是这个包的...
Linux多线程服务端编程是指在Linux操作系统环境下,使用多线程技术来实现网络服务端应用程序的技术。本文将详细介绍如何使用现代C++语言和muduo网络库在Linux平台上编写高性能的多线程TCP网络服务器,以及与此相关的...
在"io-in-action-master"这个项目中,我们可以预见到作者将展示如何使用Java IO、NIO和Netty进行实际的输入输出操作,并结合多线程并发技术来提高程序的运行效率。可能包含的内容有: 1. 使用Java IO进行文件读写、...
多线程可以有效地将这些IO密集型任务与CPU密集型任务(如解码)分离,提高程序的并发性能。 在Linux中,多线程编程还需要关注线程安全问题,如避免全局变量的不安全访问,合理管理资源分配和释放,以及正确处理异常...
引言部分指出,多线程编程是为了克服单线程程序的限制,特别是当任务可以分解为多个独立的子任务时,多线程可以显著提高执行速度。例如,网络应用程序可以创建专门的线程来处理用户请求、处理请求和发送回复,这样...
5. **强大的线程模型**:Netty 的线程模型可以根据需求调整,如单线程、多线程、工作窃取等,保证了高并发场景下的性能。 6. **易于扩展**:Netty 采用模块化设计,允许开发者方便地添加自定义协议、编解码器和处理...