一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
异步操作的本质
所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础。熟悉电脑硬件的朋友肯定对DMA 这个词不陌生,硬盘、光
驱的技术规格中都有明确DMA 的模式指标,其实网卡、声卡、显卡也是有DMA 功能的。DMA就是直接内存访问的意思,也就是说,拥有DMA 功能的硬件在和内存进行数据交换的时候
可以不消耗CPU 资源。只要CPU 在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU 时间的
I/O 操作正是异步操作的硬件基础。所以即使在DOS 这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA 操作。
线程的本质
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU 资源来运行和调度。
异步操作的优缺点
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少共享变量的数量),
减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些初入,而且难以调试。
异步调用与多线程
异步调用并不是要减少线程的开销, 它的主要目的是让调用方法的主线程不需要同步等待在这个函数调用上, 从而可以让主线程继续执行它下面的代码.与此同时, 系统会通过从
ThreadPool 中取一个线程来执行,帮助我们将我们要写/读的数据发送到网卡.由于不需要我们等待, 我们等于同时做了两件事情. 这个效果跟自己另外启动一个线程来执行等待方式
的写操作是一样的.但是, 异步线程可以利用操作系统/.Net 的线程池, 系统可以根据吞吐量动态的管理线程池的大小.
异步与多线程,从辩证关系上来看,异步和多线程并不时一个同等关系,异步是目的,多线程只是我们实现异步的一个手段.什么是异步:异步是当一个调用请求发送给被调用者,而调用者
不用等待其结果的返回.实现异步可以采用多线程技术或则交给另外的进程来处理但是未必需要一个线程来运行。例如:硬件是有DMA 功能的,在调用DMA 传输数据的时
候,CPU 是不需要执行处理的,只需要发起传输和等待传输结束即可。具体到.net 平台,比如Socket 的BeginSend,如果是运行在Windows 2000以后的平台,在底层就会调用异步的完成端口来发送。
线程池的实现方法与线程是不一样的.初始化时在线程池里的线程为0.当进程需要一个线程时,创建一个线程,由此线程执行用户的方法.需要注意的是,此线程执行完后并不立即销毁,而
是挂起等待,如果有其他方法需要执行,回唤醒进行处理.只有当它等到40秒(没有官方记录,有可能是其它数字)还没有任务执行时才唤醒自己,并销毁自己,释放资源.当然,如果线程池中
的线程不够处理任务时,会再次创建一个新线程进行执行.异步有的时候用普通的线程,有的时候用系统的异步调用功能。有一些IO 操作也是异步的,
.Net 中的异步执行其实使用的是异步委托。异步委托将要执行的方法提交到.net 的线程池,由线程池中的线程来执行异步方法。
异步执行也得执行,不在当前线程执行,当然得去另外一个线程执行。异步通常用系统线程池的线程,通常情况下性能好些。(因为可以多次利用,申请时不需要重新申请一个线程,
只需要从池里取就行了。)异步是一种效果,多线程是一种具体技术。可以说,用“多线程”实现“异步”。
异步和多线程是两个不同的概念,不能这样比较.异步请求一般用在IO 等耗时操作上,他的
好处是函数调用立即返回,相应的工作线程立即返还给系统以供重用。由于系统的线程资源是非常宝贵的,通常有一定的数目限制,如.net 默认是25。若使用异步方式,用这些固定数
目的线程在固定的时间内就可以服务更多的请求,而如果用同步方式,那么每个请求都自始至终占用这一个线程,服务器可以同时服务的请求数就少了。当异步操作执行完成后,系统
会从可用线程中选取一个执行回调程序,这时的这个线程可能是刚开始发出请求的那个线程,也可能是其他的线程,因为系统选取线程是随机的事情,所以不能说绝对不是刚开始的那个线程。
多线程是用来并发的执行多个任务。
一旦你使用.NET完成了一次异步调用,它都需要一个线程来处理异步工作内容(以下简称异步线程),异步线程不可能是当前的调用线程,因为那样仍然会造成调用线程的阻塞,与同步无异。事实上,.NET会将所有的异步请求队列加入线程池,以线程池内的线程处理所有的异步请求。对于线程池似乎不必了解的过于深入,但我们仍需要关注以下几点内容:
● Sleep()的异步调用会在一个单独的线程内执行,这个线程来自于.NET线程池。
● .NET线程池默认包含25个线程,你可以改变这个值的上限,每次异步调用都会使用其中某个线程执行,但我们并不能控制具体使用哪一个线程。
● 线程池具备最大线程数目上限,一旦所有的线程都处于忙碌状态,那么新的异步调用将会被置于等待队列,直到线程池产生了新的可用线程,因此对于大量异步请 求,我们有必要关注请求数量,否则可能造成性能上的影响。
为了暴露线程池的上限,我们修改Sleep()函数,将线程挂起的时间延长至30s。在代码的运行输出结果中,我们需要关注以下内容:
● 线程池内的可用线程数量。
● 异步线程是否来自于线程池。
● 线程托管ID值。
上文已经提到,.NET线程池默认包含25个线程,因此我们连续调用30次异步方法,这样可以在第25次调用后,看看线程池内部究竟发生了什么。