C# 多线程学习系列三之CLR线程池系列之ThreadPool

一、CLR线程池

1、进程和CLR的关系 一个进程可以只包含一个CLR,也可以包含多个CLR 2、CLR和AppDomain的关系 一个CLR可以包含多个AppDomain 3、CLR和线程池的关系 一个CLR只包含一个线程池 所以得出一个CLR下的多个AppDomain共享一个线程池和一个进程下的多个CLR拥有多个线程池的结论.注:多个线程池间的线程池相互不产生影响.

4、CLR和线程池和操作请求队列的关系 (1)、CLR第一次初始化时,线程池并没有线程,当应用程序调用异步代码执行一个方法时,会将该请求记录项加入到操作请求队列中,线程池的代码从这个队列中获取记录项,并派发给线程池线程,接着 线程池会创建线程,当然这里会有性能开销,但是当该线程执行完毕之后,线程池会回收这个线程,这里注意:线程池不会直接销毁这个线程,而是让它处于闲置状态.这样就不会产生额外的性能开销. 但是如果该线程如果长时间处于闲置状态,那么线程池会销毁它,关于这个时间的计算很复杂,各个CLR对它的定义各不相同. (2)、当应用程序向线程池发起了多个请求,线程池会尝试用一个线程来处理你所有的请求,但是如果这个线程处理压力过大,那么它会开启一个新的线程来给它分担压力.以此类推. (3)、线程池之包含了少量线程,因为如果线程太多,会增加性能开销,当然如果你升级了你电脑的cpu,线程池则会创建更多的线程.这个过程线程池会自动的去读取你得cpu核数信息,自动的去分配合适的线程数 合理地分配CPU资源.当应用程序的压力减轻,那么它会销毁不用的线程.

(4)、代码演示

     static void Main(string[] args)
        {
            Console.WriteLine("主线程开始执行,调用一个带参数的线程池子线程");
            //主线程调用ThreadPool.QueueUserWorkItem方法向线程池的操作队列添加一个记录项
            //线程池会遍历这个操作队列的所有记录项,然后将记录项中派发给一个线程池线程
            //接着线程池的线程就开始执行ExecuteOtherWork方法(同时接受了主线程传递给它的参数)
            ThreadPool.QueueUserWorkItem(ExecuteOtherWork,666);
            Console.WriteLine("主线程继续执行");
            Console.WriteLine("两个线程全部执行完毕");
            Console.Read();//这行代码必须加,因为线程池是后台线程,当进程关闭,该进程所有的后台线程都会被关闭,不管是否执行完毕.
        }

        /// <summary>
        /// 线程池子线程调用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            Console.WriteLine("线程池子线程开始执行,主线程传递给它的参数是:{0}", state);
            Console.WriteLine("线程池子线程执行完毕");
        }

注:这里的输出顺序不确定,因为在多核机器下,可能线程调度器会同时执行主线程和子线程.

四、关于线程池线程的执行上下文

(1)、什么是执行上下文

执行上下文是初始线程的环境描述的数据结构,该结构包含以下东西:

i、安全设置(压缩栈、Thread的Principal属性( 获取或设置线程的当前负责人(对基于角色的安全性而言))和Windows身份)

ii、宿主设置 详情参见HostExecutionContext、HostExecutionContextManager类,通过该类可以设置宿主上下文的状态、以及创建当前宿主上下文的副本.代码,并设置子线程的上下文为主线程的上下文:

      static void Main(string[] args)
        {
            Console.WriteLine("主线程开始执行,调用一个带参数的线程池子线程");
            var mainThreadContext = new HostExecutionContext("0");
            mainThreadContext=mainThreadContext.CreateCopy();
            var thread = new Thread(ExecuteOtherWork);
            thread.Start(mainThreadContext);
            Console.Read();
        }

        /// <summary>
        /// 线程池子线程调用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            var manager = new HostExecutionContextManager();
            manager.SetHostExecutionContext(state as HostExecutionContext);
            Console.WriteLine("子线程执行完毕");
        }

凑合着看,暂时还没有发现这么做的实际意义.可能只有微软知道。哈哈!CLR默认造成初始线程的上下文流向任何子线程。

注:关于上下文复制的这种机制,很清楚,肯定会造成性能上的开销,每开启一个新的线程就会复制原有线程的上下文给新的线程.

但是考虑到性能问题,MS提供了ExecutionContext

        private static string shareKey = "线程之间共享的数据槽值键";
        static void Main(string[] args)
        {
            
            Console.WriteLine("主线程开始执行,调用一个带参数的线程池子线程");
            CallContext.LogicalSetData(shareKey, "666");
            ExecutionContext.SuppressFlow();
            var thread = new Thread(ExecuteOtherWork);
            thread.Start();
            Console.Read();
        }

        /// <summary>
        /// 线程池子线程调用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            Console.WriteLine("线程之间共享的数据槽值:{0}", CallContext.LogicalGetData(shareKey));
            Console.WriteLine("子线程执行完毕");
        }

关于CallContext.LogicalSetData参考下面的例子

iii、逻辑调用上下文数据结构CallContext类,关于它的用法,如下:

        private static string notShareContextKey = "线程内唯一的对象,无法共享到其他线程";

        private static string shareContextKey = "线程之间共享的对象,可以传播到其他线程";

        static void Main(string[] args)
        {
            Console.WriteLine("主线程开始执行,调用一个带参数的线程池子线程");
            CallContext.SetData(notShareContextKey, "111");
            CallContext.LogicalSetData(shareContextKey, "666");
            var thread = new Thread(ExecuteOtherWork);
            thread.Start();
            Console.WriteLine("看看主线程能不能通过CallContext.SetData方法拿到这个数据:{0}", CallContext.GetData(notShareContextKey) ??"没有拿到");
            Console.WriteLine("看看主线程能不能通过CallContext.LogicalSetData方法拿到主线程的逻辑上下文对象里面设置的数据:{0}", CallContext.LogicalGetData(shareContextKey) ?? "没有拿到");
            Console.Read();
        }

        /// <summary>
        /// 线程池子线程调用的方法
        /// </summary>
        /// <param name="state"></param>
        static void ExecuteOtherWork(object state)
        {
            var obj=CallContext.GetData("线程内唯一的对象,无法共享到其他线程");
            Console.WriteLine("看看子线程能不能通过CallContext.SetData方法拿到主线程的逻辑上下文对象里面设置的数据:{0}", CallContext.GetData(notShareContextKey) ?? "没有拿到");
            Console.WriteLine("看看子线程能不能通过CallContext.LogicalSetData方法拿到主线程的逻辑上下文对象里面设置的数据:{0}", CallContext.LogicalGetData(shareContextKey) ?? "没有拿到");
            Console.WriteLine("");
            Console.WriteLine("子线程执行完毕");
        }

CallContext.SetData设置的数据线程内唯一,不能跨线程调用,但是CallContext.LogicalSetData可以跨线程调用.后者类似于HttpContext的Session机制,用于保存用户信息,不受多线程的影响,如果你希望你的数据随着线程的消失而消失可以使用前者来做,其实HttpContext上下文的本质就是使用HttpContext,我推测的,没有检验,倒是效果是一样的,本身用户的请求就相当于一个线程.所以,可以通过对这两者的理解,可以封装一个对象,该对象维持一个应用程序上下文,同时能满足Web应用,可其他基于线程池的应用.即使在多线程环境下,也能很好的维护一些应用全局共享的关键数据.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

通过扩展改善ASP.NET MVC的验证机制[使用篇]

ASP.NET MVC提供一种基于元数据的验证方式是我们可以将相应的验证特性应用到作为Model实体的类型或者属性/字段上,但是这依然具有很多的不足。在这篇文章...

20150
来自专栏田京昆的专栏

Memcached 与 Redis 实现的对比

memcached 和 redis,作为近些年最常用的缓存服务器,相信大家对它们再熟悉不过了。前两年还在学校时,我曾经读过它们的主要源码,如今写篇笔记从个人角度...

34.2K190
来自专栏惨绿少年

Redis 数据库

1.1 Redis简介 ? 1.1.1 介绍 Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对(key-value)存储数据库...

852130
来自专栏架构师之路

10w定时任务,如何高效触发超时

一、缘起 很多时候,业务有定时任务或者定时超时的需求,当任务量很大时,可能需要维护大量的timer,或者进行低效的扫描。 例如:58到家APP实时消息通道系统,...

43040
来自专栏大内老A

.NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为...

22660
来自专栏比原链

剥开比原看代码11:比原是如何通过接口/create-account创建帐户的

Gitee地址:https://gitee.com/BytomBlockchain/bytom

13010
来自专栏极客猴

Django学习之旅(四)

因为自己看了其他方面的书,所以Django的学习计划暂时搁浅。我这周重新恢复计划,Django学习之旅第四篇文章姗姗来迟。本文主要讲述视图的一些高级用法,包括 ...

10120
来自专栏更流畅、简洁的软件开发方式

【实体类变形】—— 元数据(另类ORM) 描述字段的数据

     放假了,不知道有没有加班的,先祝大家国庆节快乐!      上次说得有点乱,“行列转换”这个词可能误导了大家,那么把这个词扔掉吧。我们重新开始。假设我...

229100
来自专栏技术博客

设计模式之四(抽象工厂模式第一回合)

首先关于抽象工厂模式的学习,我们需要慢慢的,由浅入深的进入。不能单刀直入,否则可能达不到预期学明白的目标。

11910
来自专栏犀利豆的技术空间

Redis 数据库、键过期的实现

之前的文章讲解了 Redis 的数据结构,这回就可以看看作为内存数据库,Redis 是怎么存储数据的以及键是怎么过期的。

27820

扫码关注云+社区

领取腾讯云代金券