zoukankan      html  css  js  c++  java
  • 异步、多线程、任务、并行编程:选择合适的多线程模型

    本篇概述:

    @FCL4.0中已经存在的线程模型,以及它们之间异同点;

    @多线程编程模型的选择。

     

    1:异步、多线程、任务、并行的本质

    这四个概念对应在CLR中的本质,本质都是多线程。

    异步,简单的讲就是BeginInvoke、EndInvoke模式,它在CLR内部线程池进行管理;

    多线程,体现在C#中,可以由类型Thread发起。也可以由ThreadPool发起。前者不受CLR线程池管理,后者则是。FCL团队为了各种编程模型的方便,还另外提供了BackgroundWorker和若干个Timer,基本上它们都是ThreadPool的加强,增加了一些和调用者线程的交互功能;

    任务(Task),为FCL4.0新增的功能,在一个称之为任务并行库(TPL)的地方,其实也就是System.Threading.Tasks命名空间下。任务并行库名字取的很玄乎,其实它也是CLR线程池的加强。优化了线程间的调度算法,增加了和调用者线程的交互功能;

    并行(Parallel),为FCL4.0新增的功能,也属于TPL。并行在后台使用Task进行管理,说白了,因为Task使用的线程池线程,所以Parallel自然使用的也是线程池线程进行管理,它的本质仅仅是进一步简化了Task。在这里要增进一个对于并行的理解。实际上,多线程天然就是并行的。及时不用任务并行库,用Thread类型新起两个线程,CLR或者说Windows系统也会将这两个线程根据需要安排到两个CPU上去执行。所以,并不是因为多了任务并行库,CLR才支持并行计算,任务并行库只是提供了一组API,使我们能够更好的操纵线程进行并行开发而已。

    2:遗憾

    Jeffrey Richter大叔说,微软提供了这么多线程模型,是遗憾的,因为这制造了混乱。很多开发者都不知道该选用哪个类型来编写自己的多线程代码。我们对微软总是又爱又恨,它总是不停的更新一些东西,逼迫我们不停的学习。但是也好,进步导致它不会过早死掉,让我们彻底失掉饭碗。
    C#刚出来的被人笑,现在它的很多语法特性已经比Java优美。很多时候我们太擅长于嘲笑,以致最后只能哭。顺便说一句,我依然是那么的喜欢JAVA,只是很久没用它而已。

    3:现在,该用什么来编写多线程 

    如果你在FRAMEWORK4.0下编写代码,那么应该按照这个优先级来撰写多线程代码: 

    优先

    次优先

    不得以

    Parallel(含扩展库PLinq)

    Task

    ThreadPool(BackgroundWorker,Timer)

    异步

    Thread

    这个表满足了大部分情况下的一个优先级指导,但在某些情况下会有例外。

    3.1:为什么 Parallel和Task优先级一样,而不是优于Task?

    Parallel虽然在后台使用Task进行管理,并且它所谓简化了对于Task的操作,但是它有一个重要的特征区别与Task:Parallel会阻滞调用者线程。查看Paralle的成员,有For、ForEach、Invoke方法,它甚至都没提供一个BeginInvoke方法,也很好的暗示了这一点。不过虽然是同步的执行的,Parallel还是会把多个任务分配到多个CPU上去。
    Task被用的最多的是Start方法,它不会阻滞主线程。虽然Task也提供了同步的启动线程的方法RunSynchronously,但一般用的不多。 

    3.2:何时用异步,何时用线程或线程池

    这需要从“IO操作的DMA(Direct Memory Access)模式”讲起。通过DMA的数据交换几乎可以不损耗CPU的资源。在硬件部分,硬盘、网卡、声卡、显卡等都有DMA功能。可以简单的认为,当我们的工作线程需要操作I/O资源的时候(如读取一个大文件、读取一个网页、读取Socke包等),我们就需要用异步去做这些事情。异步模式只会在工作开始以及工作结束的时候占用CLR线程池,其它时候由硬盘、网卡等硬件设备来处理具体的工作,这就不会过多占用到CPU空间和时间损耗。 

    概括而言:

    计算密集型工作,直接采用线程;

    IO密集型工作,采用异步机制;

    当我们不清楚什么工作是I/O密集型的,一个不是很恰当的指导就是:查看FCL类型成员,如果成员提供了类似BeginDosomething方法的,则优先使用它,而不是新起一个线程或丢到线程池。

    3.3:线程池的优势

    新起线程,会带来很大的开销,这些开销主要集中在:分配线程内核对象、线程环境块、用户模式栈、内核模式栈所需要的内存空间,加载的DLL的DLLMain方法,并传递连接标志,以及线程上下文切换。由于线程如此昂贵,所以对于普通的开发要求来说,线程池就是一个很好的选择。线程池替开发人员管理工作线程,当一项工作完毕的时候,CLR不会销毁这个线程,而是会保留这个线程一段时间,看是否有别的工作需要这个线程。至于何时销毁或新起线程,由CLR决定。


    3.4:何时用Thread 
    以上的各种线程模型,它们最终都是Thread。 那么什么时候需要Thread直接出场呢?

    最重要的使用Thread的理由是,我们需要控制线程的优先级。Thread之上的线程模型都不支持优先级设置。设置一个线程的高优先级可以使它获得更多的CPU时间;

    再者,可以控制线程为前台线程。当然,由Thread新起的线程默认就是前台线程。前台线程不随着调用者线程的中断而中断,这使得我们可以用Thread来进行一些关键性的操作。

     
  • 相关阅读:
    PAT 解题报告 1009. Product of Polynomials (25)
    PAT 解题报告 1007. Maximum Subsequence Sum (25)
    PAT 解题报告 1003. Emergency (25)
    PAT 解题报告 1004. Counting Leaves (30)
    【转】DataSource高级应用
    tomcat下jndi配置
    java中DriverManager跟DataSource获取getConnection有什么不同?
    理解JDBC和JNDI
    JDBC
    Dive into python 实例学python (2) —— 自省,apihelper
  • 原文地址:https://www.cnblogs.com/qixuejia/p/7801581.html
Copyright © 2011-2022 走看看