读书感受 - 软件工程师 - C#线程参考手册(多线程技术分析)

简介: 这几天,花了些时间,浏览了下《C#线程参考手册》,对初学者比较有用。。。       该书可以在我CSDN下载频道获得,请购买原书支持正版(http://lzhdim.download.csdn.net/)。

      这几天,花了些时间,浏览了下《C#线程参考手册》,对初学者比较有用。。。

      该书可以在我CSDN下载频道获得,请购买原书支持正版(http://lzhdim.download.csdn.net/)。

      几年前买过一本Intel的工程师写的《多核程序设计技术》一书,本来想开个专题来对多核程序的设计做介绍的,由于时间问题,该专题改为“并行程序设计”了,但该书的重要内容却没有记录下来,比较遗憾,后续有时间再补吧。(现在叫并行程序的比较多)

      其实Intel组织开展过多次并行程序的活动和编程专题,一来推广它的多核CPU,二来对推进并行程序的设计开发做铺垫,毕竟它和微软也是老伙伴了,向来不是我的软件推动你的硬件的发展,要不就是我的硬件更多的系列来支持你的软件更新换代。

      其实对于CPU的多核的发展,我觉得是挺慢的。早在多年前,DSP的硬件就已经支持并行处理了,而且有不少的芯片系列,开发板之类的,对于那些应用早就如火如荼的开展了(当时CPU还是单核的,服务器要装几个CPU,即主板上有几个CPU插槽)。而电脑CPU的发展比较缓慢,一个是由于硬件工艺技术上的发展限制(其实也挺快了,Intel一直都是用摩尔定律来进行硬件的升级发展),主要是nm级的火拼吧;一个也是价格上的问题,毕竟新工艺在实验室里研究成功后,还需要一定的时间才能投入到生产中;一个也是前面的CPU系列的更新换代问题,厂商们需要时间来推广和销售他们对应的电脑产品,比如主板,内存之类(产品线的更新是个大问题);还有一个重要的,就是操作系统的支持。操作系统需要根据新的硬件升级,更好的发挥出硬件的能力,更多的榨取硬件的价值。操作系统的价值不仅仅在于配合硬件,更好的提供客户的体验才是最主要的。(现在GPU的发展倒是挺快,抢了CPU的风头。CPU最初的应用就是计算,结果现在倒是大幅度的应用GPU的计算能力,真是对CPU的讽刺。)

      墨迹了这么多,转入正题吧。。。

      一、说到线程,从硬件CPU开始。早期的CPU技术,单核的,比如超线程技术,它的实质是在逻辑上(不是物理)映射另一个CPU核心,然后共享CPU的缓存,以软件的分配调度方式来模拟多核的应用(硬件底层是需要底层的软件代码来支持,即芯片内部的数据处理代码,其上才是操作系统,而操作系统需要再通过设备驱动程序才能访问该硬件)。这种支持超线程的CPU,在windows任务管理器中,能够看到2个至多个CPU使用记录的显示,但其实质上仍是1/2个硬核(现在的CPU,比如4核,如果支持超线程技术,那么显示为8个CPU记录,其它类推)。Intel刚开始推出超线程CPU技术,貌似挺好,但是早期的硬件设计、驱动以及操作系统的支持问题,Intel曾一度停止该超线程技术的应用。但是到了后来,因为技术成熟了,所以又开始应用该技术到CPU里。还是实际的硬核才是真道理。

           CPU不知道什么线程,它只负责处理数据。早期的总线型技术,能够提供的带宽相对比较小,随着硬件的发展,已经限制了CPU处理数据的速度,于是,最新的QPI型技术出来了,带宽增大了,当然,目前只在X58、P55之类的主板上才支持,新技术开始总是贵的。对于CPU来说,它只知道二进制指令(RISC指令集和CISC指令集)和二进制数据,而数据的长度(位数),即32bit、64bit决定了CPU处理数据的大小。芯片级代码的算法,就已经控制和调度哪个空闲的CPU核来处理并行的数据。所以,操作系统只需要调用硬件CPU厂商提供的驱动程序,并控制线程队列的运作即可,实质上做的是中介的应用。

      二、这里描述下线程的调度顺序。 用户应用程序 -> 操作系统 -> HAL -> 驱动程序 -> 主板北桥芯片组(P55只有南桥) -> 主板总线 -> CPU核心调度算法 -> CPU指令集 -> CPU缓存 -> CPU核心 。(这个顺序是我对硬件的理解,如果大家有不同的意见,欢迎批评指正)。

           我们用C#编写的并行程序,受CLR托管,而并行程序中的线程,受操作系统的管理。.NET框架已经提供了对线程的调用的方法集,考虑了线程的创建,更新,通信,同步,数据锁,异步通信等等问题。所以,除非特别需要,尽量使用框架提供的方法来操作线程,以取得更好的性能和效率以及控制力。

           1、理解线程的生命期。

           主要是对线程的状态变化进行理解,对于后面理解线程的运行机制和使用代码控制线程提供基础。

          

           上图介绍了C#中线程的操作方法和状态。应该对该图有个印象,后面应用这些方法就简单了。

           2、理解线程所处的环境。

           要使用C#提供的线程操作功能,必须先搞清楚线程所处的运行环境。下图展示了基本环境。

          

            上图是一个简要的环境描述。其中,CLR运行于操作系统上,而托管的应用程序进程,则运行在CLR的控制下。应用程序域对应于程序集。每个域里面,可能没有线程,也可能会有多个线程。

            3、调用线程。

            C#中调用线程很简单。Thread t=new Thread(new ThreadStart(Function)); t.Start();即可。这里线程的回收,也是由GC处理。

            线程的优先级也同样比较重要,当然也不能随意设置,太多的高优先级的线程将抢占CPU资源,反而会导致操作系统性能下降。线程的同步和线程安全是需要特别注意的地方。如果处理不好,则会导致资源竞争,导致死锁等问题。

            线程的同步,.NET框架中提供了几个操作类进行处理和控制。这个需要对各个操作类的应用深入了解,选择性的进行使用,以提供应用程序性能。

            比如,对共享资源的锁定及重要代码段的锁定,一般习惯性的用下列代码来实现:

代码
1  lock object  )
2  {
3       // do something 
4       // deal with object
5  }

             这个是常用的方式,在IL中生成的代码,与使用下面的代码类似,在IL上没啥区别。

代码
1  Monitor.Enter(  object  );
2  // do something 
3  // deal with object 
4  Monitor.Exit();

             这里有个小问题,Monitor.Enter( object );该方法会在资源object争用时导致线程等待(从而就会有死锁的可能发生),所以适合于该线程处理的内容为需要等待处理结束的应用。而如果是线程对线程的调度,或者线程监控某个资源的应用下,就得使用bool b=Monitor.TryEnter( object );该方法如果获取不到资源,则b将为false,这样下面就可以根据b来做分支判断是否执行处理数据代码了,否则可以结束该线程,等待下一个新线程对该资源的访问,从而不用等待资源的释放。选择哪个应用取决于实际环境的分析和设计了。

             4、线程池技术。

             线程池技术在多线程程序的效率上节省了创建新线程的时间,转为对线程资源的调度应用上。当然,线程池也不是万能的。它主要是应用在短暂的线程运行处理上,而不适用于某个处理大且长的应用上。对于线程池的应用,直接使用.NET框架中的ThreadPool操作类即可,其内置的处理方法与操作系统的配合,是一种高效的应用线程池的方案。

             如果在特殊场合,需要自己建立线程池的话(或者存储其它与线程类似的对象的对象池),建议尽量使用HashSet<T>泛型类来进行处理,而不要使用数组的方式来进行存储。该书中就是使用了ArrayList数组来进行处理。线程池一般都固定大小,所以会使用数组来进行处理。但是ArrayList也是长度可变的数组。在对存储的内容的处理上,数组也是存储在托管堆上的,但是它的区域是连续的内存区域,这个是它的特点,也是优点。而HashSet使用的是Hash的方式进行的存储,对于存储内容的处理上,对于增减内容的操作,对比固定数组的处理上要高效,因为数组如果删除中间的某个内容,需要循环以将后续的内容来填充至该删除的区域,可能降低了效率。在此不多说了,大家可以写些DEMO来做性能判断。

            1、我举个例子。线程池就象是工厂里面的多条生产线。需要生产产品的时候,我就取一条空闲的生产线来进行处理。生产达到任务后就让这条生产线空出来,等待 下一个生产调用。如果没有空闲的生产线,那么我会让该任务等待一下再去处理,或者增加一条生产线来处理任务,实在不行,再根据任务优先级来暂停某条生产 线,优先处理现在需要处理的任务。。。
           2、对于线程池中的线程,使用完后不是释放它的资源,而是让它空闲出来。是我这个“增减”没有描述清楚,是一个实现方式的问题,才导致了你的误会,下面说一下。
           3、那么,线程池怎么实现呢?
           如果使用一个固定长度的数组来实现的话,那么,就需要循环遍历数组来查找空闲可用的线程,在多个请求空闲线程的时候,还需要锁定该线程资源来保证线程安全等等。。。这个是一个实现方式。
          另 一个实现方式,就如书中所描述的。使用一个数组来存储已在使用线程,使用另一个数组来存储空闲的线程。请求空闲线程,直接从空闲数组中获取线程,并保存到 已使用数组中。已使用数组中的线程,完成任务后就保存到空闲数组中。这个就是我所说的线程“增减”的问题。。。这个是另一个实现方式。
至于这两种方式,效率和性能的取舍就要看大家怎么应用了。 

            5、多线程程序的调试

            VS中提供了工具,用来对多线程程序的调试提供了便利。具体请看该书的第6章。

 

            上述是说了几点,还是没有将概念讲透,请大家仔细阅读该书。

            下面给出些小参考:

            1、对于CPU硬件来说,主要的在与其运行的频率高低,决定了它的运行速度。所以,对于一个单核频率为3.0G的CPU,和一个双核2.0G的CPU,在使用单线程的应用程序,或者少量的多线程应用程序来说,由于3.0G的运行速度,那么其将比2.0G的双核CPU运行得快。而如果多线程的应用程序环境下,2.0G双核的CPU不定会比单核3.0G运行得快,这个主要是多线程程序会导致CPU频繁的切换线程,所以,不能片面的说多核的CPU就比单核的CPU速度快。对于目前最新的Core i5的四核CPU,比如2.0G频率,在硬件上已经做了优化,如果运行的主要是单线程的程序,那么它会把运行频率提高到3.0或者其它的频率,同时关闭其它的硬核,以提高运行速度。而在主要运行多线程的程序时,它会根据算法平均分配CPU资源以加快程序运行的效率。。。

            2、对于多线程程序的编写,一定要尽量少而精简的利用线程,以减少CPU对线程的调度切换的时间和效率。

            3、除了使用.NET框架提供的线程操作处理类方法外,还有其它第3方的解决方案,比如Intel就提供了第3方的组件来提供支持,这个可以参考《多核程序设计技术》一书。

            4、ASP.NET程序的运行,本身就是多线程的,所以,如果可以,建议查阅该方面底层的内容,对.NET框架如何应用多线程技术,以及如何提高效率做参考。

            5、可以查阅其它相关C#线程操作方面的书籍。或者找些C#写的网游游戏代码来做参考。这些都是多线程技术的典型应用方向。

 

            时间过得真快,转眼又到周末了,祝大家周末愉快吧。该休息的休息,该玩的玩。。。。。。

目录
相关文章
|
2天前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
17天前
|
Web App开发 Linux C#
C# 网页截图全攻略:三种技术与 Chrome 路径查找指南
本文主要介绍了在 C# 中实现网页截图的几种技术及相关要点。涵盖了 PuppeteerSharp、Selenium 和 HtmlToImage 三种方式,分别阐述了它们的安装步骤及核心代码。同时,针对在 C# 中寻找 Windows 上 chrome.exe 路径这一问题,分析了未安装 Google Chrome 和已安装两种情况下的查找原因,并给出了相关参考链接,还列举了一系列与 C# 使用 Selenium、获取 chrome.exe 路径以及在 Linux 上部署相关的参考资料。
55 11
|
2月前
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
|
2月前
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
|
2月前
|
程序员 C# 数据库
C# 比较对象新思路,利用反射技术打造更灵活的比较工具
中途接手的项目,碰到需要在更新对象信息时比较并记录差异的需求,最变态的还有附加要求,怎么办?有没有既能满足需求又能对项目影响最小的方法呢?分享这个我封装的方法,一个利用反射技术打造的更灵活的比较工具
|
2月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
74 1
|
3月前
|
人工智能 开发框架 前端开发
C#/.NET/.NET Core技术前沿周刊 | 第 12 期(2024年11.01-11.10)
C#/.NET/.NET Core技术前沿周刊 | 第 12 期(2024年11.01-11.10)
|
3月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
105 0
|
3月前
|
人工智能 开发框架 安全
C#/.NET/.NET Core技术前沿周刊 | 第 13 期(2024年11.11-11.17)
C#/.NET/.NET Core技术前沿周刊 | 第 13 期(2024年11.11-11.17)
|
3月前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
57 3