• 线程上下文切换的性能损耗测试


    线程上下文切换的性能损耗到底有多少,一直没有直观的理解,今天写个程序测试一下。先看看下面的程序(点击下载):

    image

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

       1: public abstract class ThreadTester
       2:     {
       3:         public const long MAX_COUNTER_NUMBER = 100000000;
       4:  
       5:         private long _counter = 0;
       6:  
       7:         //获得计数
       8:         public virtual long GetCounter()
       9:         {
      10:             return this._counter;
      11:         }
      12:  
      13:         //增加计数器
      14:         protected virtual void IncreaseCounter()
      15:         {
      16:             this._counter += 1;
      17:         }
      18:  
      19:         //启动测试
      20:         public abstract void Start();
      21:  
      22:         //获得Counter从开始增加到现在的数字所耗的时间
      23:         public abstract long GetElapsedMillisecondsOfIncreaseCounter();
      24:  
      25:         //测试是否正在运行
      26:         public abstract bool IsTesterRunning();
      27:     }

    SingleThreadTester是单线程计数。

       1: class SingleThreadTester : ThreadTester
       2:     {
       3:         private Stopwatch _aStopWatch = new Stopwatch();
       4:  
       5:         public override void Start()
       6:         {
       7:             _aStopWatch.Start();
       8:  
       9:             Thread aThread = new Thread(() => WorkInThread());
      10:             aThread.Start();
      11:         }
      12:  
      13:         public override long GetElapsedMillisecondsOfIncreaseCounter()
      14:         {
      15:             return this._aStopWatch.ElapsedMilliseconds;
      16:         }
      17:  
      18:         public override bool IsTesterRunning()
      19:         {
      20:             return _aStopWatch.IsRunning;
      21:         }
      22:  
      23:         private void 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:         public override void 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:         public override long GetElapsedMillisecondsOfIncreaseCounter()
      18:         {
      19:             return this._aStopWatch.ElapsedMilliseconds;
      20:         }
      21:  
      22:         public override bool IsTesterRunning()
      23:         {
      24:             return _aStopWatch.IsRunning;
      25:         }
      26:  
      27:         private void 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:         private void 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:         private readonly int _threadCount = 0;
       5:         private readonly object _counterLock = new object();
       6:         
       7:         public MultiThreadTester(int threadCount)
       8:         {
       9:             this._threadCount = threadCount;
      10:         }
      11:  
      12:         public override void 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:         public override long GetElapsedMillisecondsOfIncreaseCounter()
      24:         {
      25:             return this._aStopWatch.ElapsedMilliseconds;
      26:         }
      27:  
      28:         public override bool IsTesterRunning()
      29:         {
      30:             return _aStopWatch.IsRunning;
      31:         }
      32:  
      33:         private void 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:         static void 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:         private static string GetStatusOfThreadTester(ThreadTester tester)
      30:         {
      31:             return string.Format("[耗时{0}ms] counter = {1}, {2}",
      32:                     tester.GetElapsedMillisecondsOfIncreaseCounter(), tester.GetCounter(),
      33:                     tester.IsTesterRunning() ? "running" : "stopped");
      34:         }
      35:  
      36:         private static ThreadTester GreateThreadTesterByInputText(string inputText)
      37:         {
      38:             switch (inputText)
      39:             {
      40:                 case "1":
      41:                     return new SingleThreadTester();
      42:                 case "2":
      43:                     return new TwoThreadSwitchTester();
      44:                 default:
      45:                     return new MultiThreadTester(100);
      46:             }
      47:         }
      48:  
      49:         private static string 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之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小).

    context-switch

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

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

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

    Single Thread:

    image

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

    Two Thread Switch:

    image

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

    Multi Threads - 100 Threads:

    image

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

    Multi Threads - 2 Threads:

    image

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

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

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

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

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

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

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

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

    参考:

  • 相关阅读:
    如何学习掌握一门新技术
    Linux多线程编程(不限Linux)
    腾讯后台开发面试题2
    腾讯后台开发面试题
    【转】Linux杀死fork产生的子进程的僵尸进程defunct
    【转】Linux网络编程入门
    【转】揭开Socket编程的面纱
    【转】简单理解socket
    【转】404、500、502等HTTP状态码介绍
    【转】fread函数和fwrite函数
  • 原文地址:https://www.cnblogs.com/EthanCai/p/3705834.html
Copyright © 2020-2023  润新知