簡單理解設計模式——享元模式-線程池-任務(task)

前面在寫到多線程的文章的時候,一直想寫一篇關於線程池等一系列的文章,作一下記錄,本篇博客記錄一下設計模式中享元模式的設計思想,以及使用享元模式的實現案例——線程池,以及線程池的簡化版——任務(task)html

享元模式設計模式

在軟件開發過程當中,若是咱們須要重複使用某個對象的時候,重複的去new這樣一個對象,咱們在內存中就會屢次的去申請內存空間了,這樣,可能會出現內存使用愈來愈多的狀況。數組

若是讓咱們解決這個問題,會不會這樣想:「既然是同一個對象,能不能只建立一個對象,而後下次須要再建立這個對象的時候,讓它直接用已經建立好的對象就行了」,也就是說--讓一個對象共享!多線程

這種實現方式有點相似排版印刷術,將全部的字先提早印刷好,須要哪一個字直接拿過來用,就不用每次打印字的時候再從新造一個字的模板了,這就是我理解的享元模式的思想。架構

享元模式的正式定義:併發

運用共享技術有效的支持大量細粒度的對象,享元模式能夠避免大量相相似的開銷,在軟件開發中若是須要生成大量細粒度的類實例來表示數據,若是這些實例除了幾個參數外基本都是相同的,這個時候就可使用享元模式。若是把這些參數(指的是這是實例不一樣的參數,好比:排版印刷的時候每一個字的位置)移動到類的外面,在調用方法時把他們傳遞進來,這樣就經過共享數據,減小了單個實例的數目(這個也是享元模式的實現要領),咱們把類實例外面的參數稱之爲享元對象的外部狀態,把在享元模式內部定義稱之爲內部狀態。異步

 

享元模式的實現小demoide

 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 享元模式
{
    class Program
    {
        static void Main(string[] args)
        {
            //定義外部狀態,例如字母的位置等信息
            int externalstate = 10;
            //初始化享元工廠
            FlyweighFactory factory = new FlyweighFactory();
            //判斷是否已經建立了字母A,若是已經建立就直接使用創鍵的對象A
            Flyweight fa = factory.GetFlyweight("A");
            if (fa != null)
            {
                //把外部狀態做爲享元對象的方法調用參數
                fa.Operation(--externalstate);
            }
            //判斷是否已經建立了字母B
            Flyweight fb = factory.GetFlyweight("B");
            if (fb!=null)
            {
                fb.Operation(--externalstate);
            }
            //判斷是否已經建立了字母C
            Flyweight fc = factory.GetFlyweight("C");
            if (fc != null)
            {
                fc.Operation(--externalstate);
            }
            //判斷是否建立了字母D
            Flyweight fd = factory.GetFlyweight("D");
            if (fd != null)
            {
                fd.Operation(--externalstate);
            }
            else
            {
                Console.WriteLine("駐留池中不存在字符串D");
                //這個時候就須要建立一個對象並放入駐留池中
                ConcreteFlyweight d = new ConcreteFlyweight("D");
                factory.flyweights.Add("D", d);
            }
            Console.ReadLine();

        }
    }
    /// <summary>
    /// 享元工廠,負責建立和管理享元對象
    /// </summary>
    public class FlyweighFactory
    {
        /// <summary>
        /// 定義一個池容器
        /// </summary>
        public Hashtable flyweights = new Hashtable();
        public FlyweighFactory()
        {
            flyweights.Add("A", new ConcreteFlyweight("A"));//將對應的內部狀態添加進去
            flyweights.Add("B", new ConcreteFlyweight("B"));
            flyweights.Add("C", new ConcreteFlyweight("C"));
        }
        /// <summary>
        /// 根據鍵來查找值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public Flyweight GetFlyweight(string key)
        {
            return flyweights[key] as Flyweight;
        }
    }



    /// <summary>
    /// 抽象享元類,提供具體享元類具備的方法
    /// </summary>
    public abstract class Flyweight
    {
        public abstract void Operation(int extrinsicstate);
    }
    /// <summary>
    /// 具體享元對象,這樣咱們不把每一個字符設計成一個單獨的類了,而是把共享的字母做爲享元對象的內部狀態
    /// </summary>
    public class ConcreteFlyweight : Flyweight
    {
        /// <summary>
        /// 內部狀態
        /// </summary>
        private string intrinsicstate;
        public ConcreteFlyweight(string innerState)
        {
            this.intrinsicstate = innerState;
        }
        /// <summary>
        /// 享元類的實例方法
        /// </summary>
        /// <param name="extrinsicstate">外部狀態</param>
        public override void Operation(int extrinsicstate)
        {
            Console.WriteLine("具體實現類:intrinsicstate(內部狀態){0},extrinsicstate(外部狀態){1}", intrinsicstate, extrinsicstate);
        }
    }
}

 

享元模式的使用場景:性能

一個系統中有大量的對象;測試

這些對象耗費大量的內存

這些對象能夠按照內部狀態分紅不少組,當把外部對象從對象中剔除時,每個組均可以僅用一個對象代替

軟件系統不依賴這些對象的身份。

注意:使用享元模式須要額外的維護一個記錄子系統已有額全部享元的表,這也是耗費資源的。因此當在有足夠多的對象實例,或者這些享元實例的建立特別耗費資源的時候能夠考慮使用享元模式。

不知道你這裏有沒有發現,其實享元模式定義了一個「池「的概念。在排版印刷的時候,咱們將全部的字(內部狀態)放在一個字體池中,使用完以後將這些字(內部狀態)再放回池中。

這跟咱們接下來講的線程池彷佛不謀而合。

線程池:

先說一下後臺線程和前臺線程:二者幾乎相同,惟一的區別是,前臺線程會阻止進程的正常退出,後臺線程則不會。

線程的建立和銷燬要消耗不少時間,並且過多的線程不只會浪費內存空間,還會致使線程上下文切換頻繁,影響程序性能,爲改善這些問題,.Net運行時(CLR)會爲每一個進程開闢一個全局惟一的線程池來管理其線程。

線程池內部維護一個操做請求隊列,程序執行異步操做的時候,添加目標操做到線程池的請求隊列;線程池代碼提取記錄項並派發線程池中的一個線程;若是線程池中沒有可用線程,就建立一個新的線程,建立的新線程不會隨着任務的完成而銷燬,這樣就能夠避免線程的頻繁建立和銷燬。若是線程池中大量的線程長時間無所事事,空閒線程會進行自我終結以釋放資源。

線程池中經過保持進程中線程的少許和高效來優化程序的性能。

當線程數達到設定值且忙碌,異步任務將進入請求隊列,直到有線程空閒纔會執行

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace 線程池
{
    class Program
    {
        static void Main(string[] args)
        {
            RunThreadPoolDemo();
            Console.ReadLine();
        }

        static void RunThreadPoolDemo()
        {
            線程池.ThreadPoolDemo.ShowThreadPoolInfo();
            ThreadPool.SetMaxThreads(100, 100);//默認(1023,1000)(8核心CPU)
            ThreadPool.SetMinThreads(8, 8); // 默認是CPU核心數
            線程池.ThreadPoolDemo.ShowThreadPoolInfo();
            線程池.ThreadPoolDemo.MakeThreadPoolDoSomeWork(100);//計算限制任務
            線程池.ThreadPoolDemo.MakeThreadPoolDoSomeIOWork();//IO限制任務
        }
    }

    public class ThreadPoolDemo
    {
        /// <summary>
        /// 顯示線程池信息
        /// </summary>
        public static void ShowThreadPoolInfo()
        {
            int workThreads, completionPortThreads;

            //當前線程池可用工做線程數量和異步IO線程數量
            ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
            Console.WriteLine($"GetAvailableThreads => workThreads:{0};completionPortThreads:{1}", workThreads, completionPortThreads);
            //線程池最大可用的工做線程數量和異步IO線程數量
            ThreadPool.GetMaxThreads(out workThreads, out completionPortThreads);
            Console.WriteLine($"GetMaxThreads => workThreads:{0};completionPortThreads:{1}", workThreads, completionPortThreads);
            //出現新的請求,判斷是否須要建立新線程的依據
            ThreadPool.GetMinThreads(out workThreads, out completionPortThreads);
            Console.WriteLine($"GetMinThreads => workThreads:{0};completionPortThreads:{1}", workThreads, completionPortThreads);
            Console.WriteLine();

        }
        /// <summary>
        /// 讓線程池作些事情
        /// </summary>
        /// <param name="workCount"></param>
        public static void MakeThreadPoolDoSomeWork(int workCount = 10)
        {
            for (int i = 0; i < workCount; i++)
            {
                int index = i;
                ThreadPool.QueueUserWorkItem(s =>
                {
                    Thread.Sleep(100);//模擬工做時長
                    Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{index}]");
                    ShowAvailableThreads("WorkerThread");
                });
            }
        }

        /// <summary>
        /// 讓線程作一些IO工做
        /// </summary>
        public static void MakeThreadPoolDoSomeIOWork()
        {
            //隨便找一些能夠訪問的網址
            IList<string> urlList = new List<string>()
            {
                "http://news.baidu.com/",
                "https://www.hao123.com/",
                "https://map.baidu.com/",
                "https://tieba.baidu.com/",
                "https://wenku.baidu.com/",
                "http://fanyi-pro.baidu.com",
                "http://bit.baidu.com/",
                "http://xueshu.baidu.com/",
                "http://www.cnki.net/",
                "http://www.wanfangdata.com.cn",
            };

            foreach (var uri in urlList)
            {
                WebRequest request = WebRequest.Create(uri);
                //request包含此異步請求的狀態信息的對象
                request.BeginGetResponse(ac =>
                {
                    try
                    {
                        WebResponse response = request.EndGetResponse(ac);
                        ShowAvailableThreads("IOThread");
                        Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{response.ContentLength}]");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                },request);
            }
        }

        /// <summary>
        /// 打印線程池可用線程
        /// </summary>
        /// <param name="sourceTag"></param>
        private static void ShowAvailableThreads(string sourceTag = null)
        {
            int workThreads, completionPortThreads;
            ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
            Console.WriteLine($"{0} GetAvailableThreads => workThreads:{1};completionPortThreads:{2}",sourceTag,workThreads,completionPortThreads);
            Console.WriteLine();
        }

        /// <summary>
        /// 取消通知者
        /// </summary>
        public static CancellationTokenSource CTSource { get; set; } = new CancellationTokenSource();

        /// <summary>
        /// 執行可取消的任務
        /// </summary>
        public static void DoSomeWorkWithCancellation()
        {
            ThreadPool.QueueUserWorkItem(t =>
            {
                Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] begun running. [0 - 9999]");

                for (int i = 0; i < 10000; i++)
                {
                    if (CTSource.Token.IsCancellationRequested)
                    {
                        Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] recived the cancel token. [{i}]");
                        break;
                    }
                    Thread.Sleep(100);//模擬工做時長
                }
                Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was cancelled.");
            });
        }
    }
}

代碼中含有中文命名空間,這樣寫不規範,請不要模仿~

線程池內部維護着一個工做項隊列,這個隊列指的是線程池的全局隊列,實際上,除了全局隊列,線程池會給每一個工做者線程維護一個本地隊列

當咱們調用ThreadPool.QueueUserWorkItem方法時,工做項會被放入全局隊列;使用定時器Timer的時候,也會將工做項放入全局隊列;可是,當咱們使用任務Task的時候,假如使用默認的任務調度器,任務會被調度到工做者線程的本地隊列中。

工做者線程優先執行本地隊列中最新進入的任務,若是本地隊列中已經沒有任務,線程會嘗試從其餘工做者線程任務隊列的隊尾取任務執行,這裏須要進行同步。若是全部工做者線程的本地隊列都沒有任務能夠執行,工做者線程纔會從全局隊列取最新的工做項來執行。全部任務執行完畢後,線程睡眠,睡眠必定時間後,線程醒來並銷燬本身以釋放資源

異步IO實現過程以下:

  1. 託管的IO請求線程調用Win32本地代碼ReadFile方法
  2. ReadFile方法分配IO請求包IRP併發送至Windows內核
  3. Windows內核把收到的IRP放入對應設備驅動程序的IRP隊列中,此時IO請求線程已經能夠返回託管代碼
  4. 驅動程序處理IRP並將處理結果放入.NET線程池的IRP結果隊列中
  5. 線程池分配IO線程處理IRP結果

任務(Task)

 

我理解的任務是在線程池的基礎上進行的優化,可是任務比線程有更小的開銷和更精確的控制,任務是架構在線程之上的,就是說,任務最後仍是拋給線程去執行。

開10個任務,並不會開是個線程,這是我理解的再線程池的基礎上優化的依據。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Tesk_任務_
{
    class Program
    {
        static void Main(string[] args)
        {

            #region 建立任務
            //第一種方式開一個任務
            Task t = new Task(() =>
            {
                Console.WriteLine("任務工做開始......");
                //模擬工做過程
                Thread.Sleep(5000);
            });
            t.Start();
            //第二種方式建立任務
            Task t = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("任務工做開始......");
                Thread.Sleep(5000);
            });
            //當第一的任務工做完成以後接着執行這一步操做
            t.ContinueWith((task) =>
            {
                Console.WriteLine("任務完成,完成時的狀態爲:");
                Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
            });
            Console.WriteLine("等待任務完成!");
            Console.ReadKey();
            #endregion

            #region 任務的生命週期
            var task1 = new Task(() =>
            {
                Console.WriteLine("Begin");
                Thread.Sleep(2000);
                Console.WriteLine("Finish");
            });
            Console.WriteLine("Begin start:" + task1.Status);
            task1.Start();//開啓任務
            Console.WriteLine("After start:" + task1.Status);
            task1.Wait();
            Console.WriteLine("After Finsh:" + task1.Status);
            Console.ReadLine();
            #endregion

            #region Task的任務控制
            var task1 = new Task(() =>
                {
                    Console.WriteLine("Begin1");
                    Thread.Sleep(2000);
                    Console.WriteLine("Finish1");
                });
            var task2 = new Task(() =>
            {
                Console.WriteLine("Begin2");
                Thread.Sleep(5000);
                Console.WriteLine("Finish2");
            });

            task1.Start();//開啓任務
            task2.Start();//開啓第二個任務
            //public Task ContinueWith(Action<Task> continuationAction);
            //ContinueWith<string>:string是這個任務的返回值類型
            var result = task1.ContinueWith<string>(task =>
            {
                Console.WriteLine("task1 finished");
                return "this is task result";
            });
            //task1.Wait();//等待第一個任務完成
            //Task.WaitAll(task1, task2);
            //Console.WriteLine("All task Finshed:");
            Console.WriteLine(result.Result.ToString());
            Console.ReadLine();

            //經過ContinueWith獲取第一個任務的返回值
            var a = Task.Factory.StartNew(() => { return "One"; }).ContinueWith<string>(ss => { return ss.Result.ToString(); });

            Task b = new Task<string>(() =>
            {
                return "one";
            });
            Console.WriteLine(b.ToString());//這樣獲取不到b任務的返回值

            Console.WriteLine(a.Result);



            #region TaskContinuationOptions 定義延續任務在什麼狀況下執行
            Task<Int32> t = new Task<Int32>(i => Sum((Int32)i), 10000);
            t.Start();
            //TaskContinuationOptions建立延續任務的行爲,OnlyOnRanToCompletion只有當前面的任務執行完才能安排延續任務
            t.ContinueWith(task => Console.WriteLine("The sum is:{0}", task.Result), TaskContinuationOptions.OnlyOnRanToCompletion);

            //OnlyOnFaulted延續任務前面的任務出現了異常纔會安排延續任務,將任務中的錯誤信息打印出來了
            t.ContinueWith(task => Console.WriteLine("Sum throw:{0}", task.Exception), TaskContinuationOptions.OnlyOnFaulted);
            //OnlyOnCanceled延續任務前面的任務已取消的狀況下才會安排延續任務
            t.ContinueWith(task => Console.WriteLine("Sum was cancel:{0}", task.IsCanceled), TaskContinuationOptions.OnlyOnCanceled);
            try
            {
                t.Wait();
            }
            catch (AggregateException)
            {

                Console.WriteLine("出錯");
            }
            #endregion

            #region AttachedToParnt枚舉類型(父任務)
            Task<Int32[]> parent = new Task<int[]>(() =>
            {
                var results = new Int32[3];
                new Task(() => results[0] = Sum(1000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[1] = Sum(2000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => results[2] = Sum(3000), TaskCreationOptions.AttachedToParent).Start();
                return results;
            });
            //任務返回的是一個數組,我要作的是對數組進行打印ForEach(),
            var cwt = parent.ContinueWith(parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
            parent.Start();
            cwt.Wait();
            #endregion

            #region 取消任務
            CancellationTokenSource cts = new CancellationTokenSource();
            Task<Int32> t = new Task<int>(() => Sum(cts.Token, 1000), cts.Token);
            //能夠如今開始,也能夠之後開始
            t.Start();
            //在以後的某個時間,取消CancellationTokenSource 以取消Task
            cts.Cancel();//這個是異步請求,Task可能已經完成了
            //註釋這個爲了測試拋出的異常
            //Console.WriteLine("This sum is:", t.Result);
            try
            {
                //若是任務已經取消了,Result會拋出AggregateException
                Console.WriteLine("This sum is:", t.Result);
            }
            catch (AggregateException x)
            {
                x.Handle(e => e is OperationCanceledException);
                Console.WriteLine("Sum was Canceled");
                
            }

            #endregion
            Console.ReadLine();
            #endregion
        }


        private static Int32 Sum(Int32 i)
        {
            Int32 sum = 0;
            for (; i >0 ; i--)
            {
                checked { sum += i; }
            }
            return sum;
        }
        private static Int32 Sum(CancellationToken ct, Int32 i)
        {
            Int32 sum = 0;
            for (; i >0; i--)
            {
                //在取消標誌引用的CancellationTokenSource上若是調用
                //Cancel,下面這一行就會拋出OperationCanceledException
                ct.ThrowIfCancellationRequested();
                checked { sum += i; }
            }
            return sum;
        }
    }
}

注意:這裏的代碼並非複製就能夠執行的,以前作demo測試的時候將全部的代碼都糅雜在一塊兒了!

關於技術與業務我也糾結過一段時間,是業務重要仍是技術重要,後來發現,技術是服務於業務的,設計模式是技術嗎?其實它是爲了解決某種實現場景總結出來的。業務和技術應該是相輔相成的,在工做中不免會遇到一些重複性的工做,可不能夠嘗試着改進在工做中的實現方式來提升本身的技術水平呢?加油~ 追夢人!

 

參考文章:

https://www.cnblogs.com/chenbaoshun/p/10566124.html

https://www.cnblogs.com/zhili/p/FlyweightPattern.html

設計模式相關網頁:

https://www.cnblogs.com/caoyc/p/6927092.html

相關文章
相關標籤/搜索