`
blueswind8306
  • 浏览: 127261 次
  • 来自: ...
社区版块
存档分类
最新评论

并发编程系列-多线程IO vs 单线程IO

阅读更多
当有很多个文件需要进行处理的时候,我们为了提高程序执行的性能,往往想当然的开多个线程并行执行文件的读/写动作。但是其实这种“想当然”是错误的,下面我们就来看看,对于磁盘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,也会影响系统性能。
  • 大小: 35.2 KB
分享到:
评论

相关推荐

    C++-多线程编程总结-实例讲解.doc

    C++的多线程编程是提高程序效率的关键技术之一,特别是在需要高吞吐量、并发性和实时性的场景中。在C++中实现多线程,我们可以利用标准库中的`&lt;thread&gt;`头文件以及相关的同步机制,如互斥锁、条件变量等。 1. **...

    人工智能-项目实践-多线程-Java多线程高并发实例.zip

    在本项目实践中,我们将深入探讨Java中的多线程与高并发技术,特别是在人工智能领域的应用。Java作为一种广泛应用的编程语言,其强大的并发处理能力是其在大规模数据处理和高性能计算场景中备受青睐的原因之一。本...

    高性能网络编程--IO 完成端口

    它使用完成端口来管理多个线程处理多个套接字的I/O操作,极大地提高了系统的并发处理能力。 #### 基本编程实践例子 以下是一个简单的客户端代码示例,用于展示如何建立连接、发送数据及接收响应: ```c #include ...

    Python-Python3爬虫系列的理论验证比较同步依序下载多进程并发多线程并发和asyncio异步编程之间的效率差别

    本主题将深入探讨Python3中四种不同的并发模型:同步依序下载、多进程(multiprocessing)、多线程(multithreading)以及asyncio异步编程,并通过理论分析和实际案例对比它们之间的效率差异。 1. 同步依序下载: ...

    商业编程-源码-重叠IO实现的大文件读写.zip

    1. **I/O完成端口(IOCP)**:IOCP是Windows提供的一种多线程并发处理I/O请求的方法。它允许多个线程共享一个完成端口,当I/O操作完成后,系统会将完成信息放入队列,由线程从队列中取出并处理。这样,即使在处理...

    Java并发编程和多线程的区别

    Java并发编程和多线程是两个紧密相关的概念,但它们之间存在着重要的区别。并发编程是一种高级的编程范式,旨在设计能够处理多个任务并行执行的程序,从而提高系统效率和资源利用率。它涵盖了一系列的技术和策略,如...

    Introduction-to-Java-IO.rar_Java IO Tutorial

    NIO的Selector组件使得单线程可以监视多个通道的状态变化,从而实现多路复用,这对于服务器端处理大量并发连接非常有用。选择器(Selector)通过注册Channel并监听其感兴趣的事件(如连接就绪、数据可读、写操作完成...

    高并发编程实战1,2,3阶段

    通过理论讲解与实战演练相结合的方式,深入剖析了多线程编程的核心原理及其在实际项目中的应用策略。 #### 第一阶段:基础理论与实践入门 ##### 1. 高并发编程概述 - **定义**:高并发编程是指系统能够同时处理大量...

    计算机网络课程设计----多线程Web服务器

    本课程设计的主题是“多线程Web服务器”,这是一项深入理解网络编程、并发处理和服务器性能优化的重要实践。多线程技术在Web服务器中的应用能够显著提高服务器处理请求的能力,尤其是在高并发场景下。 首先,我们要...

    java基础(多线程,IO,集合,网络编程,泛型)

    本资源包“java基础(多线程,IO,集合,网络编程,泛型)”提供了对Java核心技术的全面介绍,包括五个核心主题:多线程、输入/输出(IO)、集合框架、网络编程和泛型。以下是对这些主题的详细讲解: 1. **多线程**: - ...

    Netty案例集锦(并发编程篇)-完整目录.pdf

    从简单的单线程模型到复杂的主从多线程模型,Netty提供了灵活多样的解决方案来应对各种应用场景下的并发挑战。接下来,文档将继续深入讨论具体的案例分析,帮助读者更全面地掌握Netty并发编程的相关知识。

    多IO线程框架简介

    - **多IO线程**: 通过使用多个IO线程来接收和处理网络请求,以提高系统并发能力和响应速度。 - **工作线程池**: 接收的请求会被推送到请求队列中,再由工作线程池中的线程处理,从而有效利用多核处理器资源。 - **...

    java多线程和并发.pdf

    Java多线程与并发编程是Java语言中用于处理多任务执行的关键技术,它能够帮助开发者设计出能够有效应对高并发请求的应用程序。在现代的线上(Online)和离线(Offline)应用中,合理利用多线程技术可以大幅提高系统...

    IO和线程java输入输出 多线程

    输入/输出处理数据的传输,而多线程则涉及程序的并发执行。 首先,让我们深入理解Java的IO系统。Java.IO包包含了大量用于处理输入和输出操作的类和接口。输入流(InputStream)和输出流(OutputStream)是这个包的...

    Linux多线程服务端编程:使用muduo C++网络库.pdf

    Linux多线程服务端编程是指在Linux操作系统环境下,使用多线程技术来实现网络服务端应用程序的技术。本文将详细介绍如何使用现代C++语言和muduo网络库在Linux平台上编写高性能的多线程TCP网络服务器,以及与此相关的...

    基于JAVA IO, NIO, Netty, 多线程并发实战源码.zip

    在"io-in-action-master"这个项目中,我们可以预见到作者将展示如何使用Java IO、NIO和Netty进行实际的输入输出操作,并结合多线程并发技术来提高程序的运行效率。可能包含的内容有: 1. 使用Java IO进行文件读写、...

    多线程编程例子

    多线程可以有效地将这些IO密集型任务与CPU密集型任务(如解码)分离,提高程序的并发性能。 在Linux中,多线程编程还需要关注线程安全问题,如避免全局变量的不安全访问,合理管理资源分配和释放,以及正确处理异常...

    Python多线程编程(6寸)[归纳].pdf

    引言部分指出,多线程编程是为了克服单线程程序的限制,特别是当任务可以分解为多个独立的子任务时,多线程可以显著提高执行速度。例如,网络应用程序可以创建专门的线程来处理用户请求、处理请求和发送回复,这样...

    Netty--Java IO/NIO 框架 简单学习例子

    5. **强大的线程模型**:Netty 的线程模型可以根据需求调整,如单线程、多线程、工作窃取等,保证了高并发场景下的性能。 6. **易于扩展**:Netty 采用模块化设计,允许开发者方便地添加自定义协议、编解码器和处理...

Global site tag (gtag.js) - Google Analytics