zoukankan      html  css  js  c++  java
  • 多线程实例(二)——遍历文件夹分割文件识别文件内容

    上篇写完,感觉作为一个程序员,没有撸到底好像有点不过瘾对不对?大家都知道,C#早已进阶到8.0时代了,还用原始的Thread来写感觉有点low呀,而且通篇到最后居然还有线程最大值限制,技术控不能忍!!!

    那么本篇就干脆继续优化,理想状态是8秒,我就必须将整个过程压缩到8秒这个量级!而且尽量使用新技术。

    1.引入线程池ThreadPool,来控制线程数,提高效率。

    2.引入CountdownEvent同步基元,通过它的信号计数来确定多线程是否成功完成。

    如何获取线程池能够使用的最大线程数和最小线程数呢?

     1 static void Main(string[] args)
     2         {
     3             ThreadPool.GetMaxThreads(out nMaxThread, out nMaxThread_IO);
     4             strInfo = $"nMaxThread : {nMaxThread}, nMaxThread_async : {nMaxThread_IO}.";
     5             Console.WriteLine(strInfo);
     6             ThreadPool.GetMinThreads(out nMinThread, out nMinThread_IO);
     7             strInfo = $"nMinThread : {nMinThread}, nMinThread_async : {nMinThread_IO}.";
     8             Console.WriteLine(strInfo);
     9             Console.ReadKey();
    10         }
    View Code

     根据操作系统和CPU硬件不同,得到的值也有所不同,我们这里需要先记录下来这几个阈值,后面优化时需要用到。

    接下来,我们可以对比一下使用线程池和使用线程的性能。

     1 class Program
     2     {
     3         private static readonly Stopwatch sw = new Stopwatch();
     4         private static string strInfo;
     5 
     6         static void Main(string[] args)
     7         {
     8             sw.Start();
     9             strInfo = $"Enter Main : {sw.ElapsedMilliseconds} ms";
    10             Console.WriteLine(strInfo);
    11             const int numberOfThreads = 300;
    12             sw.Reset();
    13             sw.Start();
    14             UseThreadPool(numberOfThreads);
    15             sw.Stop();
    16             Console.WriteLine("Execution time using threadpool: {0}", sw.ElapsedMilliseconds);
    17 
    18             sw.Reset();
    19             sw.Start();
    20             UseThreads(numberOfThreads);
    21             sw.Stop();
    22             Console.WriteLine("Execution time using threads: {0}", sw.ElapsedMilliseconds);
    23             Console.ReadKey();
    24         }
    25 
    26         static void UseThreads(int numberOfThreads)
    27         {
    28             using (var countdown = new CountdownEvent(numberOfThreads))
    29             {
    30                 Console.WriteLine("Scheduling work by creating threads");
    31                 for (int i = 0; i < numberOfThreads; i++)
    32                 {
    33                     var thread = new Thread(() => {
    34                         Thread.Sleep(TimeSpan.FromSeconds(0.1));
    35                         Console.Write("{0} ", Thread.CurrentThread.ManagedThreadId);
    36                         countdown.Signal();
    37                     });
    38                     thread.Start();
    39                 }
    40                 countdown.Wait();
    41                 Console.WriteLine();
    42             }
    43         }
    44 
    45         static void UseThreadPool(int numberOfThreads)
    46         {
    47             using (var countdown = new CountdownEvent(numberOfThreads))
    48             {
    49                 Console.WriteLine("Starting work on a threadpool");
    50                 for (int i = 0; i < numberOfThreads; i++)
    51                 {
    52                     ThreadPool.QueueUserWorkItem(_ => {
    53                         Thread.Sleep(TimeSpan.FromSeconds(0.1));
    54                         Console.Write("{0} ", Thread.CurrentThread.ManagedThreadId);
    55                         countdown.Signal();
    56                     });
    57                 }
    58                 countdown.Wait();
    59                 Console.WriteLine();
    60             }
    61         }
    62     }
    View Code

     此例可以看出线程池反复利用10~15这几个线程,大量节约了创建线程,分配资源等的时间消耗。

    OK,既然验证有效,我们不妨开始按照预先的构思,开始编码吧!

     1 class Program
     2     {
     3         private static readonly Stopwatch sw = new Stopwatch();
     4         private static string strInfo;
     5 
     6         static void Main(string[] args)
     7         {
     8             sw.Start();
     9             strInfo = $"Enter Main : {sw.ElapsedMilliseconds} ms";
    10             Console.WriteLine(strInfo);
    11 
    12             string strFilefolder = "";
    13             OcrProcess(strFilefolder);            
    14             strInfo = $"Main Completed : {sw.ElapsedMilliseconds} ms";
    15             Console.WriteLine(strInfo);
    16             sw.Stop();
    17             Console.ReadKey();
    18         }
    19 
    20         static void OcrProcess(string strFilefolder)
    21         { 
    22             List<string> list_sourcefile = GetFileList(strFilefolder);
    23             using (var countdown = new CountdownEvent(list_sourcefile.Count))
    24             {
    25                 list_sourcefile.ForEach((sourcefile) =>
    26                 {                    
    27                     ThreadPool.QueueUserWorkItem(_ =>
    28                     {
    29                         strInfo = $"{sourcefile} : {sw.ElapsedMilliseconds} ms";
    30                         Console.WriteLine(strInfo);
    31                         //这里对文件进行分割
    32                         SplitProcess(sourcefile);
    33                         countdown.Signal();
    34                     });                    
    35                 });
    36                 countdown.Wait();
    37             }  
    38         }
    39 
    40         static void SplitProcess(string sourcefile)
    41         {
    42             strInfo = $"{sourcefile} Split Start : {sw.ElapsedMilliseconds} ms";
    43             Console.WriteLine(strInfo);            
    44             int nSplitNum = 6;
    45             using (var countdown = new CountdownEvent(nSplitNum))
    46             {
    47                 for (int i = 0; i < nSplitNum; i++)
    48                 {
    49                     //模拟分割单个文件的过程,花费500ms
    50                     Thread.Sleep(500);
    51                     string split_file = sourcefile + i;
    52                     strInfo = $"{split_file} Ready : {sw.ElapsedMilliseconds} ms";
    53                     Console.WriteLine(strInfo);
    54                     ThreadPool.QueueUserWorkItem(_ =>                    
    55                     {
    56                         RecognizeProcess(split_file);                        
    57                         countdown.Signal();
    58                     });                    
    59                 }
    60                 countdown.Wait();             
    61             }            
    62             strInfo = $"{sourcefile} Split Completed : {sw.ElapsedMilliseconds} ms";
    63             Console.WriteLine(strInfo);            
    64         }
    65 
    66         static void RecognizeProcess(string split_file)
    67         {
    68             //模拟识别的过程,花费5000ms
    69             Thread.Sleep(5000);
    70             strInfo = $"{split_file} OCR completed : {sw.ElapsedMilliseconds} ms";
    71             Console.WriteLine(strInfo);           
    72         }
    73 
    74         static List<string> GetFileList(string strFilefolder)
    75         {
    76             List<string> list_file = new List<string>();
    77             for (int i = 0; i <= 2; i++)
    78             {
    79                 for (int j = 0; j <= 2; j++)
    80                     list_file.Add("File" + i + j);
    81             }
    82             return list_file;
    83         }
    84 
    85     }
    View Code

     可惜,执行结果居然耗时32多。

    分析:我们的代码有两个地方都用到了线程池,第一个地方是想要同步切割原始文件时,第二个地方是每次识别切割文件时,就需要从线程池分配一个线程去识别,因为识别耗时较长。

      从这里的输出来看,231ms和232ms,239ms和247ms,759ms和780ms,1199ms和1199ms,似乎同时只有两个线程(可以理解为工人)在并发执行,推断应该是线程池的最小线程数(nMinThread=2)在起作用,也就是说,当任务数量大于工人数量(最小线程数)时,线程池每次最多派出(nMinThread)2工人,各自领取一个任务去做,其余的任务则继续在线程池里等待(当这两个工人任务完成恢复空闲时,才会被线程池指派去做后面的任务),导致了耗时

    结论:通过设置线程池的最小线程数(nMinThread=2),可以提高并发执行数,提高效率。

    要达到最快的话,9个文件的分割需要9个线程,每个原始文件分割为6个子文件,所以需要识别的文件为6*9=54个文件,每次识别需要1个线程,总共则需要63个线程,这明显也小于系统1000的阈值限制,可以放心设置,使任务不再处于等待状态。 

    1 ThreadPool.SetMinThreads(63, 63);

     最终结果:

     执行结果耗时8秒多,达到了预期,感兴趣的话可以改变最小线程数(nMinThread)的值,观察耗时的改变,加深对多线程的理解。

  • 相关阅读:
    python2和python3的区别
    开发常用命令
    类中的内置方法
    while 循环
    sql执行效率,explain 查询执行效率
    jmeter 中的 HTTP URL Re-writing Modifier
    jmeter beanshell内容
    jmeter 和 ajax
    jdbc 与 each controller 对多条查询结果的处理
    jdbc与 Beanshell PostProcessor 对多条结果的处理
  • 原文地址:https://www.cnblogs.com/chaoyazhisi/p/13208659.html
Copyright © 2011-2022 走看看