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

并发编程系列-多线程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
分享到:
评论

相关推荐

    本项目包括利用多线程、select、poll以及epoll实现的并发处理连接请求

    这两类都要使用到IO多路复用,O多路复用是指单个进程/线程就可以同时处理多个IO请求。有三个方式select、poll、epoll。 select:将文件描述符放入一个集合中,调用select时,将这个集合从用户空间拷贝到内核空间...

    深入理解Python 多线程

    利用Python的多线程,只是利用CPU上下文切换的优势,看上去像是并发,其实只是个单线程,所以说他是假的单线程。 那么什么时候用多线程呢? 首先要知道: io操作不占用CPU 计算操作占CPU,像2+5=5 Python的多线程...

    最新C++网络编程实践视频教程 陈硕

    事件驱动与多线程的取舍.mkv66. 第七层以外的实现方式.mkv67. 正确理解TCP的可靠性.mkv68. Muduo与C++11.mkv69. N皇后问题及单机求解方法.mkv70. 并行算法与MapReduce.mkv71. RPC简介与接口定义.mkv72. 代码实现与...

    Python编写的开源、多线程的网站爬虫

    Python多线程爬虫是指使用Python编程语言中的多线程技术来实现网页爬取任务的并发执行。通常在进行网页爬取时,单线程的爬虫需要逐个请求并解析每个网页,由于网络延迟和IO等待导致相应时间较长。而多线程爬虫可以...

    Java并发编程(学习笔记).xmind

    Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...

    基于libcurl的异步单线程高并发http模型-易语言

    最近在工作中用到了libcurl请求大量网页,感觉使用多线程的方式线程数太高的话会影响性能,然后就写了一个简单的基于libcurl和reactor模型的框架,实现单线程高并发,不需要考虑竞争条件的问题,提高性能的同时也能...

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

    《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...

    Python中单线程、多线程和多进程的效率对比实验实例

    python的多进程性能要明显优于多线程,因为cpython的GIL对性能做了约束。...如果是IO密集型,多线程进程可以利用IO阻塞等待时的空闲时间执行其他线程,提升效率。所以我们根据实验对比不同场景的效率 操作系统 C

    异步 redis client.rar

    其实这么说不完全正确,我们知道Redis是一个Key-Value的非关系型数据库,我们所理解的Redis单线程主要是指网络IO和K-V的读写是由一个主线程来完成的。但Redis的其他功能,比如说持久化、异步删除、集群数据同步,...

    详解Python IO口多路复用

    我一个SocketServer有500个链接连过来了,我想让500个链接都是并发的,每一个链接都需要操作IO,但是单线程下IO都是串行的,我实现多路的,看起来像是并发的效果,这就是多路复用! 概念说明: 在进行解释之前,首先...

    Redis处理高并发机制原理及实例解析

    下面重点介绍单线程设计和IO多路复用核心设计快的原因 为什么Redis是单线程的 1.官方答案 因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现...

    掌握Redis:从安装到高效数据处理的核心原理与技巧

    Redis的高性能得益于其单线程架构,这意味着其主要的网络IO和键值对读写操作由单一线程完成,从而避免了多线程环境下的上下文切换开销。此外,文档还讨论了Redis的IO多路复用机制,这是Redis处理大量并发客户端连接...

    coroutine_event.zip

    在libevent的基础上提供同步的接口,在获得异步效率的同时提供更方便的编程方式,即提供基于协程的并发模型。 green化 将IO对象进行改造以能和协程进行配合。在某种意义上,协程与线程的关系类似于线程与进程的关系...

    nodejs中实现阻塞实例

    node.js中与生俱来的单线程编程、回调函数异步式风格让我们有时喜有时忧。先说单线程,很多人会费解于node.js的单线程如何能做到高并发?这个问题不是本文重点,点到为止。澄清一点,node.js的单线程仅仅指...

    一个进程池的服务器程序

    if (write_pid() ) //避免同时有多个该程序在运行 return -1; if (pipe(fd1) ) { perror("pipe failed"); exit(-1); } if (s_pipe(fd2) ) { perror("pipe failed"); exit(-1); } int port = atoi(argv...

    libevent:libevent框架库源码阅读

    并发编程 多进程 进度组 会话 进度PV信号量同步 进程中断信号异步通知 多线程 多线程同步 多线程互斥 事件处理模式 React堆 前摄者 红黑树 AVL树 线性链表 状态机 ARM网卡驱动原理 TCP / IP 分析说明 根据函数的调用...

    ZLMediaKit编译程序MediaServer

    使用多路复用/多线程/异步网络IO模式开发,并发性能优越,支持海量客户端连接。 提供完整的MediaServer服务器,可以免开发直接部署为商用服务器。 提供完善的restful api以及web hook,支持丰富的业务逻辑。 打通了...

    nginx v1.5.9 for windows 源程序

    3 增加Windows和Linux平台下的多线程支持(Unix下尚未实现) 多线程可支持select epoll wsa和iocp网络IO模型 并支持SSL连接 通过上述改进 Nginx在Windows平台下的性能得到大幅提高 其并发连接数一般情况下可达到...

    nginx v1.5.9 for windows

    3 增加Windows和Linux平台下的多线程支持(Unix下尚未实现) 多线程可支持select epoll wsa和iocp网络IO模型 并支持SSL连接 通过上述改进 Nginx在Windows平台下的性能得到大幅提高 其并发连接数一般情况下可达到...

    aiohttp-3.6.2-cp36-cp36m-macosx_10_13_x86_64.whl

    asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持

Global site tag (gtag.js) - Google Analytics