• 多线程及线程池


      多线程的最佳示例就是采集器。本来打算以采集器为例,由于时间问题做了简易的变更,其实就是执行方法不在执行采集。

      采集器一般来说有两种,差别在于任务获取的位置。第一种轮询由采集线程来获取,第二种轮询由调度器来获取。不管哪一种,工作池的构造基本是相同的。

      先简述一下工作池的构造再来详细介绍两种采集器的差异。

         工作池:由任务队列及提供访问的方法。比如从工作池中取任务,向工作池中添加任务。注意由于是多线程访问,应该在对任务队列访问的时候加锁,否则会出现一个任务被多个线程获取导致多次执行的现象,这样不仅影响效率,而且对结果也有影响。

         第一种,由采集线程来获取任务的采集器。

         工作池代码如下:

        public class WorkPool
        {
            private static List<string> Worklist = new List<string>();
            private static object obj = new object();
            public static int WorkCount
            {
                get { return Worklist.Count; }
            }
    
            public static string GetFirstWork()
            {
                lock (obj)
                {
                    if (Worklist.Count > 0)
                    {
                        string s = Worklist[0];
                        DeleteWork();
                        return s;
                    }
                    return "";
                }
            }
    
            public void ClearWorkPool()
            {
                if (Worklist != null && Worklist.Count > 0)
                {
                    lock (obj)
                    {
                        Worklist.Clear();
                    }
                }
            }
    
            private static void DeleteWork()
            {
                if (Worklist.Count > 0)
                {
                    Worklist.RemoveAt(0);
                }
            }
    
            public static void AddWork(string work)
            {
                if (Worklist != null && !string.IsNullOrEmpty(work))
                {
                    Worklist.Add(work);
                }
                if (string.IsNullOrEmpty(work))
                {
                    throw new Exception("work is empty!");
                }
                if (Worklist == null)
                {
                    throw new Exception("工作池未实例化");
                }
            }
    
            public static void AddWorkRange(IList<string> wList)
            {
                if (Worklist!=null)
                {
                    lock (obj)
                    {
                        Worklist.AddRange(wList);
                    }
                }
            }
        }
    WorkPool

       采集线程中包括后台线程,计时器两个部分。调度器中则是只有采集线程集合。


      后台线程需要向外界提供状态访问,开关等入口。

      后台线程BackgroundWorker在采集线程初始化的时候添加dowork事件,切记此时的dowork事件是执行一次的,不要将轮询的事情交给采集线程。dowork事件一定要遵循单一原则,只做一件事。

      计时器轮询bgw的状态,如果空闲,则从工作池中获取新任务。

     
        public class Reaper
        {
            Timer timer = new Timer(1000.0);
    
            BackgroundWorker bgw = new BackgroundWorker();
    
            public string ReaperName { get; private set; }
    
            public bool IsBusy
            {
                get { return bgw.IsBusy; }
            }
    
            public Reaper(int index)
            {
                ReaperName = "Reaper" + index;
    
                timer.Elapsed += timer_Elapsed;
                bgw.DoWork += bgw_DoWork;
            }
    
            void bgw_DoWork(object sender, DoWorkEventArgs e)
            {
                string work = e.Argument.ToString();
                if (!string.IsNullOrEmpty(work))
                {
                    Console.WriteLine(string.Format("线程名:{0}  关键词:{1}  时间:{2} {3}", ReaperName, work, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), DateTime.Now.Millisecond));
                }
    
            }
    
            void timer_Elapsed(object sender, ElapsedEventArgs e)
            {
                if (!bgw.IsBusy && WorkPool.WorkCount > 0)
                {
                    string work = WorkPool.GetFirstWork();
                    bgw.RunWorkerAsync(work);
                }
            }
    
            public void Start()
            {
                timer.Start();
            }
    
            public void Stop()
            {
                timer.Stop();
            }
        }
    Reaper

      调度器则是充当一个开关的作用。根据参数或者是配置实例化采集线程,调度器打开时循环将各个采集线程打开。各采集线程在计时器工作下轮询工作池执行dowork。

        public class ReaperMaster
        {
            private List<Reaper> list = new List<Reaper>();
    
            public int ReaperCount { get; private set; }
    
            public ReaperMaster(int count)
            {
                ReaperCount = count;
                for (int i = 0; i < count; i++)
                {
                    list.Add(new Reaper(i));
                }
            }
    
            public void Start()
            {
                foreach (Reaper reaper in list)
                {
                    if (!reaper.IsBusy)
                    {
                        reaper.Start();
                    }
                }
            }
    
            public void Stop()
            {
                foreach (Reaper reaper in list)
                {
                    reaper.Stop();
                }
            }
        }
    ReaperMaster
            for (int i = 0; i < 100; i++)
                {
                    WorkPool.AddWork((i + 51).ToString());
                }
    
                ReaperMaster master=new ReaperMaster(6);
                master.Start();
    
                Console.WriteLine("End");
                Console.ReadLine();    
    调用

      第二种,由调度器来获取任务的采集器。

      工作池略有差异(没有锁),代码如下:

     public class WorkPool
        {
            private static List<string> Worklist = new List<string>();
    
            public static int WorkCount
            {
                get { return Worklist.Count; }
            }
    
            public static string GetFirstWork()
            {
    
                if (Worklist.Count > 0)
                {
                    string s = Worklist[0];
                    DeleteWork();
                    return s;
                }
                return "";
    
            }
    
            public void ClearWorkPool()
            {
                if (Worklist != null && Worklist.Count > 0)
                {
                    Worklist.Clear();
                }
            }
    
            private static void DeleteWork()
            {
                if (Worklist.Count > 0)
                {
                    Worklist.RemoveAt(0);
                }
            }
    
            public static void AddWork(string work)
            {
                if (Worklist != null && !string.IsNullOrEmpty(work))
                {
                    Worklist.Add(work);
                }
                if (string.IsNullOrEmpty(work))
                {
                    throw new Exception("work is empty!");
                }
                if (Worklist == null)
                {
                    throw new Exception("工作池未实例化");
                }
            }
    
            public static void AddWorkRange(IList<string> wList)
            {
                if (Worklist != null)
                {
                    Worklist.AddRange(wList);
    
                }
            }
        }
    WorkPool

      采集线程中包括后台线程,任务访问入口(用于采集器指定的任务)。调度器中采集线程集合和计时器两个对象。

      bgw与第一个的差别不大。

      任务的访问入口使用属性就可以了。

        public class Reaper
        {
    
            BackgroundWorker bgw = new BackgroundWorker();
    
            public string ReaperName { get; private set; }
    
            public bool IsBusy
            {
                get { return bgw.IsBusy; }
            }
    
            public Reaper(int index)
            {
                ReaperName = "Reaper" + index;
    
    
                bgw.DoWork += bgw_DoWork;
            }
    
            void bgw_DoWork(object sender, DoWorkEventArgs e)
            {
                string work = e.Argument.ToString();
                if (!string.IsNullOrEmpty(work))
                {
                    Console.WriteLine(string.Format("线程名:{0}  关键词:{1}  时间:{2} {3}", ReaperName, work, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), DateTime.Now.Millisecond));
                }
            }
    
    
            public void Start(string work)
            {
                bgw.RunWorkerAsync(work);
            }
    
            public void Stop()
            {
                bgw.CancelAsync();
            }
        }
    Reaper

      调度器中的计时器轮询采集线程集合中的采集线程的状态,如果线程空闲则获取新任务分配给此线程。调度器通过开关计时器来控制采集线程。

        public class ReaperMaster
        {
            private List<Reaper> list = new List<Reaper>();
    
            private Timer timer = new Timer(1000.0);
    
            public int ReaperCount { get; private set; }
    
            public ReaperMaster(int count)
            {
                ReaperCount = count;
                for (int i = 0; i < count; i++)
                {
                    list.Add(new Reaper(i));
                }
                timer.Elapsed += timer_Elapsed;
            }
    
            void timer_Elapsed(object sender, ElapsedEventArgs e)
            {
                foreach (Reaper reaper in list)
                {
                    if (!reaper.IsBusy && WorkPool.WorkCount > 0)
                    {
                        string work = WorkPool.GetFirstWork();
                        reaper.Start(work);
                    }
                }
            }
    
            public void Start()
            {
                timer.Start();
            }
    
            public void Stop()
            {
                timer.Stop();
            }
        }
    ReaperMaster

      调用方法同第一种。

      两种采集器还有个明显的差别,第一种采集线程运行后,由于有计时器的存在,线程可以持续运行。而第二种采集线程需要调度器不断的从外部来唤醒执行。两种方法各有优劣,第一种线程开启后可以自己执行,但是关闭的时候需要调度器循环访问挂起关闭。第二种只需要关闭调度器的计时器即可将所有的采集线程关闭挂起,但是需要调度器不断的进行唤醒线程。第一种控制力不强,这样调度器的线程则不会有太多的负担,第二种集中控制无疑给调度器线程增加了负担。由于第二种采集器只有调度器一个线程访问工作池,故工作池可以不必lock。
      采集器可以由多个工作池多个调度器组合成更加复杂的采集器。比如基于搜索引擎的采集器。一个工作池和调度器负责关键词,一个工作池和调度器负责采集搜索引擎的搜索结果,再有一个工作池和调度器对搜索引擎搜索结果进行访问采集,最后将采集结果保存。此时至少有三个线程池和调度器进行拼接,建议采用多个线程池,而不是合并线程池增加采集线程的功能。诚然在一个线程中足以实现获取关键词、获取搜索引擎搜索结果、访问搜索结果获取最终原始采集数据一整套流程,但是这样会使开发调试难度成倍增加。
    多线程的调试难度比较大,可以尝试单元测试。而且多线程的开发中单一原则的使用会使代码更加易懂,另外注意异常日志的记录。

    ----------------------------------------------------无量的分割线---------------------------------------------------------------

    一年多未更新博客,由于技术进步比较缓慢,工作生活进入慢速期,当持之以恒,共勉之。

  • 相关阅读:
    转 Android之Broadcast, BroadcastReceiver(广播)
    Android之“==”与equals区别
    Android之notificaction使用
    android service 学习
    Android之Menu.add()
    (转)Android生命周期
    Partial Method in VB.NET
    如何侦测机器上装的.net framework的版本
    Powersehll: match ,cmatch,imatch命令
    Office Tip(1) : Split the Screen
  • 原文地址:https://www.cnblogs.com/cnDqf/p/6404861.html
Copyright © 2020-2023  润新知