23.2 高效线程使用圣典
以我个人经验来看,开发人员在他们的应用程序中都过量使用了线程.
严格来讲,线程的系统开销很大。创建一个线程的开销不小于:系统必须为线程分配并初始化一个线程内核对象,还必须为每个线程保留1MB的地址空间(按需提交)用于线程的用户模式堆栈,分配12KB(左右)的地址空间用于线程的内核模式堆栈。然后,紧接着线程创建之后,Windows调用进程中的每个DLL都有的一个函数来通知进程中所有的DLL操作系统创建了一个新的线程。同样,销毁一个线程的开销也不小————进程的每个DLL都要接收一个关于该线程即将“死亡”的通知,而且内核对象及堆栈还需要释放。
23.3 线程池简介
线程池的伟大之处在于它管理着创建较少的线程以避免浪费资源与创建较多的线程以利用多处理器、超线程处理器和多内核处理器之间的平衡。另外,线程池是启发式的,如果应用程序需要执行许多任务,而且CPU的数量足够使用,那么线程池将创建更多的线程。如果应用程序的工作负载降下来,那么线程池中的线程将终止自己。
从内部实现上讲,线程池将线程澉中的线程进行分类。划分为工作线程(WorkThread)和I /O线程(I/O Thread)。当应用程序请示线程池执行一个受计算限制的异步操作(包括初始化受I/O限制的异步操作)时使用工作线程,而I/O线程用天在受I/O限制的异步操作完成时通知代码。具体而言,这意味着我们需要使用异步编程模型(Asynchronous Programming Model , APM)来进行I/O请求,例如访问文件、网络服务器、数据库、Web服务以及其他硬件设备等。本章后面,我们将计论如何执行受计算限制的异步操作以及如何使用APM执行受I/O限制的异步操作。
23.8 异步编程模型简介
执行异步操作是构建高性能、可扩展应用程序的关键,它允许我们能够用非常少的线程执行许多操作。加上线程池,异步操作允许我们利用机器中的所有CPU。Microsoft CLR 团队已经意识到这里存在着许多潜在的问题,因此着手设计了一种模式,可以让开发人员方便地利用这种能力。这种模式称为异步编程模式。
23.9 使用APM执行受I/O限制的异步操作
为了从FileStream对象中同步地读取字节,我们可以调用它的Read方法,该方法的原型如下所示:
Public Int32 Read(Byte[] array , Int32 offset , Int32 count)
为了从文件中异步地读取字节,可以调用FileStream的BeginRead方法:
IAsyncResult BeginRead(Byte[] array, Int32 offset , Int32 numBytes , AsyncCallback userCallback , Object stateObject)
调用BeginRead方法时,我们在请求Windows将从文件中读取的字节填充到字节数组中。因为要执行I/O操作,所以BeginRead方法实际上将操作请求加入到Windows设备驱动程序的队列中,而Windows的设备驱动程序知道如何与正确的硬件设备通信。就这样,硬件接管了该操作,也就不需要任何线程来执行任何操作,甚至还不需要等待输出结果------ 这种方法相当高效!
BeginRead方法返回一个其类型实现了System。IAsyncResult接口的对象的引用。调用BeginRead方法时,它构建一个对象来惟一地标识I/O操作请求,并将请求加入Windows设备驱动程序的队列,然后将IAsyncResult对象返回给我们。我们可以将IAsyncResult对象看作收据。当BeginRead方法返回时,I/O操作只是被排队,它还没有完成。因此,我们还不能操作字节数组中的字节,因为数组中还有没有包含所请求的数据。
实际上,数组中可能已经包含了所请求的数据,因为I/O操作已经被异步地执行了,因此,在BeginRead方法返回时,数据可能已经被读取了。但是数据也有可能在几秒钟之后从服务器读取过来,因外,还有可能到服务器的网络连接已经瘫痪,数据永远也不会被读取过来。因为所有上西游记情误解都有可能发生,所以我们需要一种方法来发现实际上发生了哪一种情况,而且还要知道结果是什么时候检测到的,我们将这种情况称为异步操作结果的聚集(rendezvousing)。如前所述,APM提供了三种异步操作结果的聚集技巧,我们稍后将逐一阐述这三种聚集技巧并比较它们之间的不同
评论