多线程为什么跑的比单线程还要慢的情况分析及验证

2014-05-04 07:56:50cnblogs.com-Ethan Cai-点击数: 306

“多个人干活比一个人干活要快,多线程并行执行也比单线程要快”这是我学习编程长期以来的想法。然而在实际的开发过程中,并不是所有情况下都是这样。先看看下面的程序(点击下载):

ThreadTester是所有Tester的基类。所有的Tester都干的是同样一件事情,把counter增加到100000000,每次只能加1。

   1:publicabstractclass ThreadTester
   2:     {
   3:publicconstlong MAX_COUNTER_NUMBER = 100000000;
   4:  
   5:privatelong _counter = 0;
   6:  
   7://获得计数
   8:publicvirtuallong GetCounter()
   9:         {
  10:returnthis._counter;
  11:         }
  12:  
  13://增加计数器
  14:protectedvirtualvoid IncreaseCounter()
  15:         {
  16:this._counter += 1;
  17:         }
  18:  
  19://启动测试
  20:publicabstractvoid Start();
  21:  
  22://获得Counter从开始增加到现在的数字所耗的时间
  23:publicabstractlong GetElapsedMillisecondsOfIncreaseCounter();
  24:  
  25://测试是否正在运行
  26:publicabstractbool IsTesterRunning();
  27:     }

SingleThreadTester是单线程计数。

   1:class SingleThreadTester : ThreadTester
   2:     {
   3:private Stopwatch _aStopWatch = new Stopwatch();
   4:  
   5:publicoverridevoid Start()
   6:         {
   7:             _aStopWatch.Start();
   8:  
   9:             Thread aThread = new Thread(() => WorkInThread());
  10:             aThread.Start();
  11:         }
  12:  
  13:publicoverridelong GetElapsedMillisecondsOfIncreaseCounter()
  14:         {
  15:returnthis._aStopWatch.ElapsedMilliseconds;
  16:         }
  17:  
  18:publicoverridebool IsTesterRunning()
  19:         {
  20:return _aStopWatch.IsRunning;
  21:         }
  22:  
  23:privatevoid WorkInThread()
  24:         {
  25:while (true)
  26:             {
  27:if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  28:                 {
  29:                     _aStopWatch.Stop();
  30:break;
  31:                 }
  32:  
  33:this.IncreaseCounter();
  34:             }
  35:         }
  36:     }

TwoThreadSwitchTester是两个线程交替计数。

   1:class TwoThreadSwitchTester : ThreadTester
   2:     {
   3:private Stopwatch _aStopWatch = new Stopwatch();
   4:private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
   5:  
   6:publicoverridevoid Start()
   7:         {
   8:             _aStopWatch.Start();
   9:  
  10:             Thread aThread1 = new Thread(() => Work1InThread());
  11:             aThread1.Start();
  12:  
  13:             Thread aThread2 = new Thread(() => Work2InThread());
  14:             aThread2.Start();
  15:         }
  16:  
  17:publicoverridelong GetElapsedMillisecondsOfIncreaseCounter()
  18:         {
  19:returnthis._aStopWatch.ElapsedMilliseconds;
  20:         }
  21:  
  22:publicoverridebool IsTesterRunning()
  23:         {
  24:return _aStopWatch.IsRunning;
  25:         }
  26:  
  27:privatevoid Work1InThread()
  28:         {
  29:while (true)
  30:             {
  31:                 _autoResetEvent.WaitOne();
  32:
  33:this.IncreaseCounter();
  34:  
  35:if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  36:                 {
  37:                     _aStopWatch.Stop();
  38:break;
  39:                 }
  40:  
  41:                 _autoResetEvent.Set();
  42:             }
  43:         }
  44:  
  45:privatevoid Work2InThread()
  46:         {
  47:while (true)
  48:             {
  49:                 _autoResetEvent.Set();
  50:                 _autoResetEvent.WaitOne();
  51:this.IncreaseCounter();
  52:  
  53:if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  54:                 {
  55:                     _aStopWatch.Stop();
  56:break;
  57:                 }
  58:             }
  59:         }
  60:     }

MultiThreadTester可以指定线程数,多个线程争抢计数。

   1:class MultiThreadTester : ThreadTester
   2:     {
   3:private Stopwatch _aStopWatch = new Stopwatch();
   4:privatereadonlyint _threadCount = 0;
   5:privatereadonlyobject _counterLock = newobject();
   6:
   7:public MultiThreadTester(int threadCount)
   8:         {
   9:this._threadCount = threadCount;
  10:         }
  11:  
  12:publicoverridevoid Start()
  13:         {
  14:             _aStopWatch.Start();
  15:  
  16:for (int i = 0; i < _threadCount; i++)
  17:             {
  18:                 Thread aThread = new Thread(() => WorkInThread());
  19:                 aThread.Start();
  20:             }
  21:         }
  22:  
  23:publicoverridelong GetElapsedMillisecondsOfIncreaseCounter()
  24:         {
  25:returnthis._aStopWatch.ElapsedMilliseconds;
  26:         }
  27:  
  28:publicoverridebool IsTesterRunning()
  29:         {
  30:return _aStopWatch.IsRunning;
  31:         }
  32:  
  33:privatevoid WorkInThread()
  34:         {
  35:while (true)
  36:             {
  37:lock (_counterLock)
  38:                 {
  39:if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
  40:                     {
  41:                         _aStopWatch.Stop();
  42:break;
  43:                     }
  44:  
  45:this.IncreaseCounter();
  46:                 }
  47:             }
  48:         }
  49:     }

Program的Main函数中,根据用户的选择来决定执行哪个测试类。

   1:class Program
   2:     {
   3:staticvoid Main(string[] args)
   4:         {
   5:  
   6:string inputText = GetUserChoice();
   7:  
   8:while (!"4".Equals(inputText))
   9:             {
  10:                 ThreadTester tester = GreateThreadTesterByInputText(inputText);
  11:                 tester.Start();
  12:  
  13:while (true)
  14:                 {
  15:                     Console.WriteLine(GetStatusOfThreadTester(tester));
  16:if (!tester.IsTesterRunning())
  17:                     {
  18:break;
  19:                     }
  20:                     Thread.Sleep(100);
  21:                 }
  22:  
  23:                 inputText = GetUserChoice();
  24:             }
  25:  
  26:             Console.Write("Click enter to exit...");
  27:         }
  28:  
  29:privatestaticstring GetStatusOfThreadTester(ThreadTester tester)
  30:         {
  31:returnstring.Format("[耗时{0}ms] counter = {1}, {2}",
  32:                     tester.GetElapsedMillisecondsOfIncreaseCounter(), tester.GetCounter(),
  33:                     tester.IsTesterRunning() ? "running" : "stopped");
  34:         }
  35:  
  36:privatestatic ThreadTester GreateThreadTesterByInputText(string inputText)
  37:         {
  38:switch (inputText)
  39:             {
  40:case"1":
  41:returnnew SingleThreadTester();
  42:case"2":
  43:returnnew TwoThreadSwitchTester();
  44:default:
  45:returnnew MultiThreadTester(100);
  46:             }
  47:         }
  48:  
  49:privatestaticstring GetUserChoice()
  50:         {
  51:             Console.WriteLine(@"==Please select the option in the following list:==
  52: 1. SingleThreadTester
  53: 2. TwoThreadSwitchTester
  54: 3. MultiThreadTester
  55: 4. Exit");
  56:  
  57:string inputText = Console.ReadLine();
  58:  
  59:return inputText;
  60:         }
  61:     }

三个测试类,运行结果如下:

Single Thread:

[耗时407ms] counter = 100000001, stopped

[耗时453ms] counter = 100000001, stopped

[耗时412ms] counter = 100000001, stopped

Two Thread Switch:

[耗时161503ms] counter = 100000001, stopped

[耗时164508ms] counter = 100000001, stopped

[耗时164201ms] counter = 100000001, stopped

Multi Threads - 100 Threads:

[耗时3659ms] counter = 100000001, stopped

[耗时3950ms] counter = 100000001, stopped

[耗时3720ms] counter = 100000001, stopped

Multi Threads - 2 Threads:

[耗时3078ms] counter = 100000001, stopped

[耗时3160ms] counter = 100000001, stopped

[耗时3106ms] counter = 100000001, stopped

什么是线程上下文切换

上下文切换的精确定义可以参考: http://www.linfo.org/context_switch.html。多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,为了让用户感觉这些任务正在同时进行,操作系统的设计者巧妙地利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能,但同时也带来了保存现场和加载现场的直接消耗。(Note.
更精确地说, 上下文切换会带来直接和间接两种因素影响程序性能的消耗. 直接消耗包括: CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉; 间接消耗指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小).

根据上面上下文切换的定义,我们做出下面的假设:

  1. 之所以TwoThreadSwitchTester执行速度最慢,因为线程上下文切换的次数最多,时间主要消耗在上下文切换了,两个线程交替计数,每计数一次就要做一次线程切换。
  2. “Multi Threads - 100 Threads”比“Multi Threads - 2 Threads”开的线程数量要多,导致线程切换次数也比后者多,执行时间也比后者长。

由于Windows下没有像Linux下的vmstat这样的工具,这里我们使用Process Explorer看看程序执行的时候线程上线文切换的次数。

Single Thread:

计数期间,线程总共切换了580-548=32次。(548是启动程序后,初始的数值)

Two Thread Switch:

计数期间,线程总共切换了33673295-124=33673171次。(124是启动程序后,初始的数值)

Multi Threads - 100 Threads:

计数期间,线程总共切换了846-329=517次。(329是启动程序后,初始的数值)

Multi Threads - 2 Threads:

计数期间,线程总共切换了295-201=94次。(201是启动程序后,初始的数值)

从上面收集的数据来看,和我们的判断基本相符。

干活的其实是CPU,而不是线程

再想想原来学过的知识,之前一直以为线程多干活就快,简直是把学过的计算机原理都还给老师了。真正干活的不是线程,而是CPU。线程越多,干活不一定越快。

那么高并发的情况下什么时候适合单线程,什么时候适合多线程呢?

适合单线程的场景:单个线程的工作逻辑简单,而且速度非常快,比如从内存中读取某个值,或者从Hash表根据key获得某个value。Redis和Node.js这类程序都是单线程,适合单个线程简单快速的场景。

适合多线程的场景:单个线程的工作逻辑复杂,等待时间较长或者需要消耗大量系统运算资源,比如需要从多个远程服务获得数据并计算,或者图像处理。

例子程序:http://pan.baidu.com/s/1c05WrGO

参考:

  • Context Switch – Wikipedia
  • 多线程的代价
  • Threading in C#
  • 为什么我要用 Node.js? 案例逐一介绍
  • 知乎——redis是个单线程的程序,为什么会这么快呢?每秒10000?这个有点不解,具体是快在哪里呢?EPOLL?内存?多线程
  • 从Java视角理解系统结构(一)CPU上下文切换

如下是自己写的多线程验证代码:

1.Junit测试类

import java.io.IOException;

import java.io.InputStream;

import java.net.SocketException;

import java.util.ArrayList;

import org.apache.commons.net.ftp.FTPClient;

import org.junit.Test;

import com.Thread.MyThread;

public class MainTest {

private int count = 10000;

private int countTest = 10000;

//用主线程遍历ArrayList(结论:如果循环中不sleep,速度最快,如果循环中sleep,则遍历速度比多线程慢)

@Test

public void test() throws InterruptedException {

long startTime = System.currentTimeMillis();

ArrayList<Integer>  testList = new ArrayList<Integer>();

ArrayList<Integer>  testList1 = new ArrayList<Integer>();

for(int i=0; i<count; i++){

testList.add(i);

}

for(int j=0; j<countTest; j++){

testList1.add(j);

}

System.out.println("testList.size():" + testList.size() + "   i=" + 0);

for(int j=0; j<testList.size(); j++){

Thread.sleep(2);

System.out.println(Thread.currentThread().getName() + " :" + testList.get(j));

}

for(int jj=0; jj<testList1.size(); jj++){

Thread.sleep(2);

System.out.println(Thread.currentThread().getName() + " :" + testList.get(jj));

}

System.out.println("程序总共花费时间:" + (System.currentTimeMillis()-startTime) + "毫秒");

}

//在主线程中启动一个子线程去遍历ArrayList(结论:在循环中如果不sleep,遍历速度仅次于主线程,如果循环中sleep,速度与主线程遍历差不多)

@Test

public void test0() {

long startTime = System.currentTimeMillis();

ArrayList<Integer>  testList = new ArrayList<Integer>();

ArrayList<Integer>  testList1 = new ArrayList<Integer>();

for(int j=0; j<countTest; j++){

testList1.add(j);

}

for(int i=0; i<count; i++){

testList.add(i);

}

System.out.println("testList.size():" + testList.size() + "   i=" + 0);

Runnable run0 = new MyThread(testList,0, count);

Thread t0 = new Thread(run0, "Thread-0");

long startTime1 = System.currentTimeMillis();

t0.start();

System.out.println("线程启动时间:" + (System.currentTimeMillis() - startTime1));

try {

t0.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("程序总共花费时间:" + (System.currentTimeMillis()-startTime) + "毫秒");

}

//在主线程中启动两个子线程去遍历ArrayList(结论:如果在循环中不sleep,速度最慢,如果在循环中sleep,则遍历速度最快)

@Test

public void test1() {

long startTime = System.currentTimeMillis();

ArrayList<Integer>  testList = new ArrayList<Integer>();

ArrayList<Integer>  testList1 = new ArrayList<Integer>();

for(int i=0; i<count; i++){

testList.add(i);

}

for(int j=0; j<countTest; j++){

testList1.add(j);

}

System.out.println("testList.size():" + testList.size() + "   i=" + 0);

Runnable run1 = new MyThread(testList,0, count/2);

Thread t1 = new Thread(run1, "Thread-1");

t1.start();

Runnable run2 = new MyThread(testList,(count/2+1), count);

Thread t2 = new Thread(run2, "Thread-2");

t2.start();

try {

t1.join();

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("程序总共花费时间:" + (System.currentTimeMillis()-startTime) + "毫秒");

}

//用单线程从ftp上下载两个文件

@Test

public void FileUpload() {

long startTime = System.currentTimeMillis();

try {

FTPClient ftp = new FTPClient();

ftp.connect("192.168.173.156", 21);

ftp.login("admin", "123456");

InputStream in = ftp.retrieveFileStream("ax.java");

byte[] buffer = new byte[1024];

while((in.read(buffer))!=-1){

//System.out.println(Arrays.toString(buffer));

}

in.close();

FTPClient ftp1 = new FTPClient();

ftp1.connect("192.168.173.156", 21);

ftp1.login("admin", "123456");

InputStream in1 = ftp1.retrieveFileStream("ay.java");

byte[] buffer1 = new byte[1024];

while((in1.read(buffer1))!=-1){

//System.out.println(Arrays.toString(buffer));

}

in1.close();

} catch (SocketException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println("程序总共花费时间:" + (System.currentTimeMillis()-startTime) + "毫秒");

}

//用两个线程从ftp上下载两个文件(结论:是多线程下载速度比单线程下载速度快)

@Test

public void FileUpload1() {

long startTime = System.currentTimeMillis();

/* Runnable run1 = new MyThread("ax.java");

Thread t1 = new Thread(run1, "Thread-1");

t1.start();

Runnable run2 = new MyThread("ay.java");

Thread t2 = new Thread(run2, "Thread-2");

t2.start();

try {

t1.join();

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("程序总共花费时间:" + (System.currentTimeMillis()-startTime) + "毫秒");*/

}

}

2、多线程实现类

package com.Thread;

import java.io.IOException;

import java.io.InputStream;

import java.net.SocketException;

import java.util.ArrayList;

import java.util.Arrays;

import org.apache.commons.net.ftp.FTPClient;

public class MyThread implements Runnable{

private ArrayList<Integer> intList = new ArrayList<Integer>();

private int startNum;

private int endNum;

private String filename;

public MyThread(ArrayList<Integer> intList, int startNum, int endNum) {

super();

this.intList = intList;

this.startNum = startNum;

this.endNum = endNum;

}

@Override

public void run() {

System.out.println("线程"+Thread.currentThread().getName()+"开始启动……");

System.out.println("intList.size():" + intList.size() + "   startNum=" + startNum + "    endNum=" + endNum);

for(int m=startNum; m<endNum;m++){

System.out.println(Thread.currentThread().getName() + " : " + intList.get(m));

try {

Thread.sleep(2);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

System.out.println("线程"+Thread.currentThread().getName()+"执行结束……");

/*try {

FTPClient ftp = new FTPClient();

ftp.connect("192.168.173.156", 21);

ftp.login("admin", "123456");

InputStream in = ftp.retrieveFileStream(filename);

byte[] buffer = new byte[1024];

while((in.read(buffer))!=-1){

//System.out.println(Arrays.toString(buffer));

}

in.close();*/

}

/*public MyThread(String filename) {

super();

this.filename = filename;

}*/

}

时间: 2024-10-26 12:58:02

多线程为什么跑的比单线程还要慢的情况分析及验证的相关文章

多线程为什么跑的比单线程还要慢?

"多个人干活比一个人干活要快,多线程并行执行也比单线程要快"这是我学习编程长期以来的想法.然而在实际的开发过程中,并不是所有情况下都是这样.先看看下面的程序(点击下载): ThreadTester是所有Tester的基类.所有的Tester都干的是同样一件事情,把counter增加到100000000,每次只能加1. 1: public abstract class ThreadTester 2: { 3: public const long MAX_COUNTER_NUMBER =

多线程程序跑久了效率下降分析

最近在写一个搜索引擎,有个中间程序是分析分词结果文件,建立倒排索引.最初写的是单线程的,效率低到无语,于是又改成多线程的了.本以为万事大吉,可是在分析了将近2000文件的时候,效率低的和单线程的没什么区别了.打开任务管理器,线程数显示3(我设置的子线程数量最高为15,加上启动就有的,程序刚运行的时候线程数可以达到20个). 百度了下,Windows单个程序的线程数是有上限的,一般只能开到2000个左右.而我的程序中为了方便,把每个子线程都设置为detach状态了.这个状态下,线程结束时其他线程并

多媒体开发之---live555的多线程支持,原本只是单线程,单通道

1)我对Live555进行了一次封装,但是Live555 是单线程的,里面定义的全局变量太多,我封装好dll库后,在客户端调用,因为多个对话框中要使用码流,我就定义了多个对象从设备端接收码流,建立多个连接,但是当一路码流退出,然后在退出另外的一路码流时,库里面出现问题,原因是Live555 里面的全局变量被破坏了! 针对上面问题:我目前的解决办法是将全局的信息隔离: 定义一个结构: #define CLIENT_STREAM_NUM 4 class ourRTSPClient; typedef

python制作wifi破解(跑字典(单线程))

很鸡巴简单,接上一篇文章 import pywifi import sys import time from pywifi import const def test_wifi_connect(passwordstr): wifi=pywifi.PyWiFi()#初始化 ifaces=wifi.interfaces()[0]#创建取出第一个无限网卡 #print(ifaces.name())#输出无限网卡名 ifaces.disconnect()#断开无限网卡连接 time.sleep(3)#断

四、TestNG 批量执行脚本Runner.xml - 并可以设置多线程并发跑脚本,提高跑脚本效率

C:\Users\Administrator.IntelliJIdea2019.1\system 拷贝这个路径下的xml文件到项目 <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> 原文地址:https://www.cnblogs.com/surenliu/p/12388891.html

iOS多线程中,队列和执行的排列组合结果分析

本文是对以往学习的多线程中知识点的一个整理. 多线程中的队列有:串行队列,并发队列,全局队列,主队列. 执行的方法有:同步执行和异步执行.那么两两一组合会有哪些注意事项呢? 如果不是在董铂然博客园看到这边文章请 点击查看原文 提到多线程,也就是四种,pthread,NSthread,GCD,NSOperation 其中phtread是跨平台的.GCD和NSOperation都是常用的,后者是基于前者的. 但是两者区别:GCD的核心概念是将一个任务添加到队列,指定任务执行的方法,然后执行. NSO

多线程读取大文件,尤其是对日志文件分析很有用。

我在之前的公司里工作的时候,他们要求我做一个能够分析IIS日志的程序,可我做来做去,也只能做到,1个G的文件读取在140秒左右.愁了很久,想到了用多线程读取大文件的方法,又发现文件读取流可以有很多个,于是我就开始编写,写出来,其效率 4核CPU,可以达到14秒,提高了10倍. 而且据我测试发现,我4个CPU,用8个线程读取效率最高,所以我想会不会是一个CPU挂2个文件流效率最高,故基本逻辑是这样的,我根据CPU核数,将文件分成8块,分成8块的时候,必定出现,一行被截断的情况,针对这种情况,我采用

多线程(十五、ConcurrentHashMap原理二类和方法分析)

ConcurrentHashMap的构造 ConcurrentHashMap,采用了一种"懒加载"的模式,只有到首次插入键值对的时候,才会真正的去初始化table数组. 构造方法: 1.空构造函数,默认桶大小162.指定桶初始容量的构造器,必须是2次幂值 /** * 指定table初始容量的构造器. * tableSizeFor会返回大于入参(initialCapacity + (initialCapacity >>> 1) + 1)的 最小2次幂值 */ publi

Python 多进程、多线程效率比较

Python 界有条不成文的准则: 计算密集型任务适合多进程,IO 密集型任务适合多线程.本篇来作个比较. 通常来说多线程相对于多进程有优势,因为创建一个进程开销比较大,然而因为在 python 中有 GIL 这把大锁的存在,导致执行计算密集型任务时多线程实际只能是单线程.而且由于线程之间切换的开销导致多线程往往比实际的单线程还要慢,所以在 python 中计算密集型任务通常使用多进程,因为各个进程有各自独立的 GIL,互不干扰. 而在 IO 密集型任务中,CPU 时常处于等待状态,操作系统需要