菜菜呀,我最近研究技术呢,发现线上一个任务程序线程数有点多呀

CEO,CTO,CFO于一身的CXO

x总,你学编程呢?

菜菜

作为公司总负责人,我以后还要管理技术部门呢,怎么能不会技术呢

CEO,CTO,CFO于一身的CXO

(技术部完了)。。。。。。。

菜菜

赶紧看看线上那个线程特别多的程序,给你2个小时优化一下

CEO,CTO,CFO于一身的CXO

x总,我想辞职

菜菜

菜菜呀,心不要浮躁,学学小马,心平气和养养生

CEO,CTO,CFO于一身的CXO

............................

菜菜

好了,给你半天时间把线程多的问题优化一下,要不然扣你绩效

CEO,CTO,CFO于一身的CXO

(嘞了个擦)。。。。。。

菜菜
◆◆
原因排查
◆◆

经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收mq消息的一个服务,程序大体思路是这样的,监听的线程每次收到一条消息,就启动一个线程去执行,每次启动的线程都是新的。说到这里,咱们就谈一谈这个程序有哪些弊端呢:

1.  每次收到一条消息都创建一个新的线程,要知道线程的资源对于系统来说是很昂贵的,消息处理完成还要销毁这个线程。

2.  这个程序用到的线程数量是没有限制的。当线程到达一定数量,程序反而因线程在cpu切换开销的原因处理效率降低。无论的你的服务器cpu是多少核心,这个现象都有发生的可能。

◆◆
解决问题
◆◆

线程多的问题该怎么解决呢,增加cpu核心数?治标不治本。对于开发者而言,最为常用也最为有效的是线程池化,也就是说线程池。

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。

线程池其中一项很重要的技术点就是任务的队列,队列虽然属于一种基础的数据结构,但是发挥了举足轻重的作用。

◆◆
队列
◆◆

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

队列是一种采用的FIFO(first in first out)方式的线性表,也就是经常说的先进先出策略。

实现

数组

队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针:head,队头指针,指向实际队头元素+1的位置;tail,队尾指针,指向实际队尾元素位置。一般情况下,两个指针的初值设为0,这时队列为空,没有元素。以下为一个简单的实例(生产环境需要优化):

public class QueueArray<T>
    {
        //队列元素的数组容器
        T[] container = null;
        int IndexHeader, IndexTail;
        public QueueArray(int size)
        {
            container = new T[size];
            IndexHeader = 0;
            IndexTail = 0;
        }
        public void Enqueue(T item)
        {
            //入队的元素放在头指针的指向位置,然后头指针前移
            container[IndexHeader] = item;
            IndexHeader++;
        }
        public T Dequeue()
        {
            //出队:把尾元素指针指向的元素取出并清空(不清空也可以)对应的位置,尾指针前移
            T item = container[IndexTail];
            container[IndexTail] = default(T);
            IndexTail++;
            return item;
        }     }

链表

队列采用的FIFO(first in first out),新元素总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由元素连接而成,遍历也方便。以下是一个实例仅供参考:

public class QueueLinkList<T>
    {
        LinkedList<T> contianer = null;
        public QueueLinkList()
        {
            contianer = new LinkedList<T>();
        }
        public void Enqueue(T item)
        {
            //入队的元素其实就是加入到队尾
            contianer.AddLast(item);
        }
        public T Dequeue()
        {
            //出队:取链表第一个元素,然后把这个元素删除
            T item = contianer.First.Value;
            contianer.RemoveFirst();
            return item;
        }     }

队列的扩展阅读

1. 队列通过数组来实现的话有什么问题吗?是的。首先基于数组不可变本质的因素(具体可参考菜菜之前的文章),当一个队列的元素把数组沾满的时候,数组扩容是有性能问题的,数组的扩容过程不只是开辟新空间分配内存那么简单,还要有数组元素的copy过程,更可怕的是会给GC造成极大的压力。如果数组比较小可能影响比较小,但是当一个数组比较大的时候,比如占用500M内存的一个数组,数据copy其实会造成比较大的性能损失。

2. 队列通过数组来实现,随着头指针和尾指针的位置移动,尾指针最终会指向第一个元素的位置,也就是说没有元素可以出队了,其实要解决这个问题有两种方式,其一:在出队或者入队的过程中不断的移动所有元素的位置,避免上边所说的极端情况发生;其二:可以把数组的首尾元素连接起来,使其成为一个环状,也就是经常说的循环队列。

3. 队列在一些特殊场景下其实还有一些变种,比如说循环队列,阻塞队列,并发队列等,有兴趣的同学可以去研究一下,这里不在展开讨论。这里说到阻塞队列就多说一句,其实用阻塞队列可以实现一个最基本的生产者消费者模式。

4. 当队列用链表方式实现的时候,由于链表的首尾操作时间复杂度都是O(1),而且没有空间大小的限制,所以一般的队列用链表实现更简单

5. 当队列中无元素可出队或者没有空间可入队的时候,是阻塞当前的操作还是返回错误信息,取决于在座各位队列的设计者了。

◆◆
简单实用的线程池
◆◆

Net Core C# 版本

//线程池
    public class ThreadPool
    {
        bool PoolEnable = false; //线程池是否可用 
        List<Thread> ThreadContainer = null; //线程的容器
        ConcurrentQueue<ActionData> JobContainer = null; //任务的容器
        public ThreadPool(int threadNumber)
        {
            PoolEnable = true;
            ThreadContainer = new List<Thread>(threadNumber);
            JobContainer = new ConcurrentQueue<ActionData>();
            for (int i = 0; i < threadNumber; i++)
            {
                var t = new Thread(RunJob);
                ThreadContainer.Add(t);
                t.Start();
            }           
        }
        //向线程池添加一个任务
        public void AddTask(Action<object> job,object obj, Action<Exception> errorCallBack=null)
        {
            if (JobContainer != null)
            {
                JobContainer.Enqueue(new ActionData { Job = job, Data = obj , ErrorCallBack= errorCallBack });
            }         }
        //终止线程池
        public void FinalPool()
        {
            PoolEnable = false;
            JobContainer = null;
            if (ThreadContainer != null)
            {
                foreach (var t in ThreadContainer)
                {
                    //强制线程退出并不好,会有异常
                    //t.Abort();
                    t.Join();                    
                }
                ThreadContainer = null;
            }         }
        private  void RunJob()
        {
            while (true&& JobContainer!=null&& PoolEnable)
            {
                //任务列表取任务
                ActionData job=null;
                JobContainer?.TryDequeue(out job);
                if (job == null)
                {
                    //如果没有任务则休眠
                    Thread.Sleep(10);
                    continue;
                }
                try
                {
                    //执行任务
                    job.Job.Invoke(job.Data);
                }
                catch(Exception error)
                {
                    //异常回调
                    job?.ErrorCallBack(error);
                }
            }
        }
    }     public class ActionData
    {
        //执行任务的参数
        public object Data { get; set; }
        //执行的任务
        public Action<object> Job { get; set; }
        //发生异常时候的回调方法
        public Action<Exception> ErrorCallBack { get; set; }
    }

使用方法

ThreadPool pool = new ThreadPool(100);
            for (int i = 0; i < 5000; i++)
            {
                pool.AddTask((obj) =>
                {
                    Console.WriteLine($"{obj}__{System.Threading.Thread.CurrentThread.ManagedThreadId}");
                }, i, (e) =>
                {
                    Console.WriteLine(e.Message);
                });
            }
            pool.FinalPool();
            Console.Read();
 

asp.net core C#设计一个实用的线程池的更多相关文章

  1. ASP.NET Core 3.0 一个 jwt 的轻量角色/用户、单个API控制的授权认证库

    目录 说明 一.定义角色.API.用户 二.添加自定义事件 三.注入授权服务和中间件 三.如何设置API的授权 四.添加登录颁发 Token 五.部分说明 六.验证 说明 ASP.NET Core 3 ...

  2. 如何在ASP.NET Core中实现一个基础的身份认证

    注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...

  3. 在ASP.NET Core中实现一个Token base的身份认证

    注:本文提到的代码示例下载地址> How to achieve a bearer token authentication and authorization in ASP.NET Core 在 ...

  4. [转]如何在ASP.NET Core中实现一个基础的身份认证

    本文转自:http://www.cnblogs.com/onecodeonescript/p/6015512.html 注:本文提到的代码示例下载地址> How to achieve a bas ...

  5. 002.Create a web API with ASP.NET Core MVC and Visual Studio for Windows -- 【在windows上用vs与asp.net core mvc 创建一个 web api 程序】

    Create a web API with ASP.NET Core MVC and Visual Studio for Windows 在windows上用vs与asp.net core mvc 创 ...

  6. ExecutorService 建立一个多线程的线程池的步骤

    ExecutorService 建立一个多线程的线程池的步骤: 线程池的作用: 线程池功能是限制在系统中运行的线程数. 依据系统的环境情况,能够自己主动或手动设置线程数量.达到执行的最佳效果:少了浪费 ...

  7. linux网络编程-一个简单的线程池(41)

    有时我们会需要大量线程来处理一些相互独立的任务,为了避免频繁的申请释放线程所带来的开销,我们可以使用线程池 1.线程池拥有若干个线程,是线程的集合,线程池中的线程数目有严格的要求,用于执行大量的相对短 ...

  8. JavaScript日历控件开发 C# 读取 appconfig文件配置数据库连接字符串,和配置文件 List<T>.ForEach 调用异步方法的意外 ef 增加或者更新的习惯思维 asp.net core导入excel 一个二级联动

    JavaScript日历控件开发   概述 在开篇之前,先附上日历的代码地址和演示地址,代码是本文要分析的代码,演示效果是本文要实现的效果代码地址:https://github.com/aspwebc ...

  9. 开始使用ASP.NET Core - 创建第一个Web应用

    .NET Core 是.NET Framework的新一代跨平台应用程序开发框架,是微软在一开始发展时就开源的软件平台,由于 .NET Core 的开发目标是跨平台的 .NET 平台,因此 .NET ...

随机推荐

  1. ubuntu 安装PG10 更新packet 创建超级账号

    ubuntu 安装PG10 更新packet 创建超级账号 安装pg10 我的环境是16.04 server版本 MAC和windows 建议使用安装包 直接官网下载 echo 'deb http:/ ...

  2. JHipster技术栈理解 - UAA原理分析

    本文简要分析了UAA的认证机制和部分源码功能. UAA全称User Account and Authentication. 相关源码都是通过Jhipster生成,包括UAA,Gateway,Ident ...

  3. Spark之UDAF

    import org.apache.spark.sql.{Row, SparkSession} import org.apache.spark.sql.expressions.{MutableAggr ...

  4. iOS application/json上传文件等

    在和sever后台交互的过程中.有时候.他们需要我们iOS开发者以“application/json”形式上传. NSString *accessUrl = [NSString stringWithF ...

  5. python 实现网页 自动登录

    完整代码: 1 from apscheduler.schedulers.blocking import BlockingScheduler 2 from selenium import webdriv ...

  6. LeetCode算法题-Valid Anagram(Java实现)

    这是悦乐书的第198次更新,第205篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第61题(顺位题号是242).给定两个字符串s和t,写一个函数来确定t是否是s的anag ...

  7. LeetCode算法题-Same Tree(Java实现)

    这是悦乐书的第162次更新,第164篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第21题(顺位题号是100).给定两个二叉树,编写一个函数来检查它们是否相同.如果两个二 ...

  8. Linux之命令进阶

    Linux系统的启动过程 1.开机自检 BIOS2.MBR引导3.GRUB菜单4.加载内核5.运行init进程6.从/etc/inittab读取运行级别7.根据/etc/rc.sysinit 初始化系 ...

  9. 只有 assignment、call、increment、decrement 和 new 对象表达式可用作语句

    错误信息:只有 assignment.call.increment.decrement 和 new 对象表达式可用作语句: 分析:发生这种情况一般是在赋值时把“=”写成了“==”,例如:textBox ...

  10. Python:Day05 作业

    购物车: product_list = [['iphone6s',5800],['mac book',9800],['coffee',32],['book',80],['bike',1500]] sh ...