简单理解设计模式——享元模式-线程池-任务(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

相关文章
相关标签/搜索