.NET中的工作者和I / O线程的简单描述
在.NET中很难find对worker和I / O线程的详细而简单的描述
关于这个话题,我很清楚(但在技术上可能并不精确):
- 工作者线程是应该为他们的工作使用CPU的线程;
- I / O线程(也称为“完成端口线程”) 应该使用设备驱动程序进行工作,实质上“什么都不做”,只监视非CPU操作的完成情况。
什么不明确:
- 虽然方法ThreadPool.GetAvailableThreads返回两种types的可用线程的数量,似乎没有公共API来安排I / O线程的工作。 你只能在.NET中手动创build工作线程?
- 看来,单个I / O线程可以监视多个I / O操作。 这是真的吗? 如果是这样,为什么ThreadPool在默认情况下有很多可用的I / O线程?
- 在一些文本中,我读到了在I / O线程完成I / O操作完成后触发的callback。 这是真的吗? 考虑到这个callback是CPU操作,这对于工作线程来说不是一个工作吗?
- 更具体的 – 做ASP.NETasynchronous页面用户I / O线程? 切换I / O工作来分离线程,而不是增加最大工作线程数,性能究竟有什么好处? 是否因为单个I / O线程监视多个操作? 或者,当使用I / O线程时,Windows会更有效地切换上下文吗?
.net / CLR中的术语“工作者线程”通常指的是主线程以外的任何线程,它们代表产生线程的应用程序执行一些“工作”。 “工作”可能意味着什么,包括等待一些I / O完成。 ThreadPool保留了工作线程的caching,因为线程创build起来非常昂贵。
.net / CLR中的术语“I / O线程”是指ThreadPool为了从“重叠的”win32调用(也称为“完成端口I / O”)调度NativeOverlappedcallback而保留的线程。 CLR维护自己的I / O完成端口,并可以绑定任何句柄(通过ThreadPool.BindHandle API)。 示例: http : //blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx 。 许多.net API在内部使用这种机制来接收NativeOverlappedcallback,尽pipe典型的.net开发者不会直接使用它。
“工作者线程”和“I / O线程”之间确实没有技术上的区别 – 他们都只是普通的线程。 但是CLR ThreadPool保持了每个池的独立池,以避免工作线程的高需求耗尽所有可用于分派本地I / Ocallback的线程,可能导致死锁。 (想象一下,使用全部250个工作线程的应用程序,其中每个线程都等待一些I / O完成)。
开发人员在处理I / Ocallback时需要小心,以确保I / O线程返回到ThreadPool – 也就是说,I / Ocallback代码应该执行服务callback所需的最less工作然后将线程的控制权返回给CLR线程池。 如果需要更多的工作,那么这个工作应该安排在工作者线程上。 否则,应用程序有可能“劫持”CLR的保留I / O完成线程池,以用作普通工作线程,导致上述死锁情况。
一些很好的参考文献进一步阅读:win32 I / O完成端口: http : //msdn.microsoft.com/en-us/library/aa365198( VS.85) .aspxpipe理线程池: http : //msdn.microsoft.com /en-us/library/0ka9477y.aspx BindHandle的示例: http : //blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx
我将首先描述NT中程序如何使用asynchronousI / O。
您可能熟悉Win32 API函数ReadFile (作为示例),它是Native API函数NtReadFile的一个包装。 这个函数允许你用asynchronousI / O做两件事情:
- 您可以创build一个事件对象并将其传递给NtReadFile 。 当读操作完成时,这个事件将被发信号通知。
- 您可以将一个asynchronous过程调用(APC)函数传递给NtReadFile 。 实际上,这意味着当读取操作完成时,函数将排队到启动操作的线程,并在线程执行可警告的等待时执行。
然而,当I / O操作完成时,还有第三种通知方式。 您可以创buildI / O完成端口对象,并将文件句柄与它关联。 每当在与I / O完成端口关联的文件上完成操作时,操作的结果(如I / O状态)就排队到I / O完成端口。 然后,您可以设置一个专用的线程来删除队列中的结果,并执行相应的任务,如调用callback函数。 这实质上就是“I / O工作者线程”。
正常的“工作者线程”非常相似, 不是从队列中移除I / O结果,而是从队列中删除工作项。 您可以对工作项( QueueUserWorkItem )进行排队,并让工作线程执行它们。 这可以防止你每次想asynchronous执行一个任务时产生一个线程。
简单地说,一个工作者线程就是要执行一个短时间的工作,并且在完成之后会自行删除。 callback可以用来通知父进程它已经完成或者传回数据。
I / O线程将一直执行相同的操作或一系列操作,直到父进程停止。 这是所谓的,因为它通常设备驱动程序连续运行监视设备端口。 一个I / O线程通常会创build事件,只要它希望与其他线程通信。
所有进程都以线程运行。 您的应用程序作为线程运行。 任何线程都可能会产生工作线程或I / O线程(就像您调用它们一样)。
性能和使用的线程数量或types之间总是保持良好的平衡。 过程处理的callback或事件过多将严重降低其性能,因为处理它们时主循环的中断次数较多。
工作者线程的例子是在用户交互之后将数据添加到数据库中,或者执行长时间的math计算或将数据写入文件。 通过使用工作者线程,您可以释放主应用程序,这对GUI非常有用,因为它在执行任务时不会冻结。
比我更有技能的人会跳进来帮忙。
工作线程有很多的状态,他们是由处理器等计划,你控制他们所做的一切。
IO完成端口由操作系统为涉及很less共享状态的特定任务提供,因此使用速度更快。 .Net中的一个很好的例子就是WCF框架。 对WCF服务的每个“调用”实际上都是由一个IO完成端口执行的,因为它们是最快启动的,操作系统会为你服务。