在阅读了http://blog.stephencleary.com/2013/11/there-is-no-thread.html之后,其中声明:
当设备完成其工作后,(IO操作)-它通过中断通知CPU。 ...但是,“完成”状态只存在于OS级别;进程有自己的内存空间,必须通知它。 ...由于库/BCL使用的是标准的P/Invoke重叠I/O系统,它已经向I/O完成端口(IOCP)注册了句柄,这是线程池的一部分。...因此,I/O线程池线程是借用的来执行APC,它通知任务已经完成。
我对这个大胆的部分很感兴趣:
如果我正确理解,在IO操作完成后,它必须通知执行IO操作的实际进程。
问题1:
这是否意味着它为每个已完成的IO操作抓取一个新的线程池线程?或者这是一个专门的线程数?
问题2:
看:
for (int i=0;i<1000;i++)
{
PingAsync_NOT_AWAITED(i); //notice not awaited !
}这是否意味着我将在这里同时运行1000个IOCP线程池线程,当所有线程都完成时?
发布于 2015-02-24 08:15:44
这有点宽泛,所以让我谈谈以下几点:
IOCP线程位于单独的线程池上,也就是说,这就是I/O线程设置。这样,它们就不会与用户线程池线程冲突(就像在普通await操作或ThreadPool.QueueWorkerItem中的线程池线程一样)。
就像普通的线程池一样,随着时间的推移,它只会缓慢地分配新线程。因此,即使同时出现异步响应的峰值,也不会有1000个I/O线程。
在一个正确的异步应用程序中,您的内核数量不会超过给定或接受的数量,就像工作线程一样。这是因为要么您正在执行重要的CPU工作,然后将其发送到一个普通的工作线程上,要么您正在执行I/O工作,您应该将其作为异步操作来完成。
这样做的想法是,您在I/O回调中花费的时间非常少--您不阻塞,也不做大量的CPU工作。如果违反了这一点(例如,将Thread.Sleep(10000)添加到回调中),那么是的,随着时间的推移,.NET会创建大量的IO线程--但这只是不恰当的使用。
现在,I/O线程与普通CPU线程有什么不同?它们几乎是一样的,它们只是等待一个不同的信号--它们都是(简化警报),只是一个while循环,在一个方法上提供控制,当一个新的工作项被应用程序的其他部分(或OS)排队时。主要的区别是I/O线程使用IOCP队列(OS管理的),而普通的工作线程有自己的队列,完全由应用程序程序员管理和访问。
另外,请不要忘记,您的请求可能已经同步完成。也许您是在time循环中从TCP流读取数据,一次读取512字节。如果套接字缓冲区中有足够的数据,则多个ReadAsync可以立即返回,而无需进行任何线程切换。这通常不是一个问题,因为在一个典型的应用程序中,I/O往往是最耗时的事情,所以不必等待I/O通常是可以的。但是,依赖于某些部分异步发生的错误代码(尽管这并不能保证)可以很容易地破坏应用程序。
发布于 2015-02-24 08:16:36
这是否意味着它为每个已完成的IO操作抓取一个新的线程池线程?或者这是一个专门的线程数?
为每一个I/O请求创建一个新线程的效率会非常低,以至于无法达到这个目的。相反,运行时从少量线程开始(确切数量取决于您的环境),并根据需要添加和删除工作线程(相应的算法也因环境而异)。.NET的主要版本在这个实现中已经发生了变化,但是基本思想是一样的:运行时尽力创建和维护尽可能多的线程,这是高效服务所有I/O所必需的。在我的系统(Windows8.1,.NET 4.5.2)上,一个全新的控制台应用程序在进入Main时只有3个线程,在实际工作被请求之前,这个数目不会增加。
这是否意味着我将在这里同时运行1000个IOCP线程池线程,当所有线程都完成时?
不是的。当发出I/O请求时,线程将等待一个完成端口来获取结果,并调用任何已注册的回调来处理结果(无论是通过BeginXXX方法还是作为任务的延续)。如果您使用一个任务而不等待它,那么该任务就会在那里结束,并且线程将返回到线程池。
如果你等着呢?1000个I/O请求的结果实际上不会同时到达,因为中断并不是同时到达的,但假设间隔比处理它们所需的时间短得多。在这种情况下,线程池将继续旋转线程以处理结果,直到达到最大值为止,任何进一步的请求都将在完成端口上排队。根据您如何配置它,这些线程可能需要一些时间来旋转。
考虑以下(故意令人讨厌的)玩具程序:
static void Main(string[] args) {
printThreadCounts();
var buffer = new byte[1024];
const int requestCount = 30;
int pendingRequestCount = requestCount;
for (int i = 0; i != requestCount; ++i) {
var stream = new FileStream(
@"C:\Windows\win.ini",
FileMode.Open, FileAccess.Read, FileShare.ReadWrite,
buffer.Length, FileOptions.Asynchronous
);
stream.BeginRead(
buffer, 0, buffer.Length,
delegate {
Interlocked.Decrement(ref pendingRequestCount);
Thread.Sleep(Timeout.Infinite);
}, null
);
}
do {
printThreadCounts();
Thread.Sleep(1000);
} while (Thread.VolatileRead(ref pendingRequestCount) != 0);
Console.WriteLine(new String('=', 40));
printThreadCounts();
}
private static void printThreadCounts() {
int completionPortThreads, maxCompletionPortThreads;
int workerThreads, maxWorkerThreads;
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads);
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
Console.WriteLine(
"Worker threads: {0}, Completion port threads: {1}, Total threads: {2}",
maxWorkerThreads - workerThreads,
maxCompletionPortThreads - completionPortThreads,
Process.GetCurrentProcess().Threads.Count
);
}在我的系统(它有8个逻辑处理器)上,输出如下(结果在您的系统上可能有所不同):
Worker threads: 0, Completion port threads: 0, Total threads: 3
Worker threads: 0, Completion port threads: 8, Total threads: 12
Worker threads: 0, Completion port threads: 9, Total threads: 13
Worker threads: 0, Completion port threads: 11, Total threads: 15
Worker threads: 0, Completion port threads: 13, Total threads: 17
Worker threads: 0, Completion port threads: 15, Total threads: 19
Worker threads: 0, Completion port threads: 17, Total threads: 21
Worker threads: 0, Completion port threads: 19, Total threads: 23
Worker threads: 0, Completion port threads: 21, Total threads: 25
Worker threads: 0, Completion port threads: 23, Total threads: 27
Worker threads: 0, Completion port threads: 25, Total threads: 29
Worker threads: 0, Completion port threads: 27, Total threads: 31
Worker threads: 0, Completion port threads: 29, Total threads: 33
========================================
Worker threads: 0, Completion port threads: 30, Total threads: 34当我们发出30个异步请求时,线程池很快就会使8个线程可用来处理结果,但在此之后,线程池只会以每秒大约2次的悠闲速度旋转新线程。这说明,如果要正确利用系统资源,最好确保I/O处理快速完成。实际上,让我们将我们的委托改为以下内容,它代表了对请求的“适当”处理:
stream.BeginRead(
buffer, 0, buffer.Length,
ar => {
stream.EndRead(ar);
Interlocked.Decrement(ref pendingRequestCount);
}, null
);结果:
Worker threads: 0, Completion port threads: 0, Total threads: 3
Worker threads: 0, Completion port threads: 1, Total threads: 11
========================================
Worker threads: 0, Completion port threads: 0, Total threads: 11同样,结果在您的系统和不同的运行中可能会有所不同。在这里,我们几乎看不到正在运行的完成端口线程,而我们发出的30个请求是在没有旋转新线程的情况下完成的。您应该发现可以将"30“更改为"100”,甚至更改为"100000":我们的循环不能比它们完成的速度更快地启动请求。但是,请注意,结果对我们非常有利,因为"I/O“一遍又一遍地读取相同的字节,并且将从操作系统缓存中得到服务,而不是从磁盘读取。这并不是为了演示实际的吞吐量,当然,这只是开销的差异。
要对工作线程而不是完成端口线程重复这些结果,只需将FileOptions.Asynchronous更改为FileOptions.None即可。这使得文件访问是同步的,异步操作将在工作线程上完成,而不是使用完成端口:
Worker threads: 0, Completion port threads: 0, Total threads: 3
Worker threads: 8, Completion port threads: 0, Total threads: 15
Worker threads: 9, Completion port threads: 0, Total threads: 16
Worker threads: 10, Completion port threads: 0, Total threads: 17
Worker threads: 11, Completion port threads: 0, Total threads: 18
Worker threads: 12, Completion port threads: 0, Total threads: 19
Worker threads: 13, Completion port threads: 0, Total threads: 20
Worker threads: 14, Completion port threads: 0, Total threads: 21
Worker threads: 15, Completion port threads: 0, Total threads: 22
Worker threads: 16, Completion port threads: 0, Total threads: 23
Worker threads: 17, Completion port threads: 0, Total threads: 24
Worker threads: 18, Completion port threads: 0, Total threads: 25
Worker threads: 19, Completion port threads: 0, Total threads: 26
Worker threads: 20, Completion port threads: 0, Total threads: 27
Worker threads: 21, Completion port threads: 0, Total threads: 28
Worker threads: 22, Completion port threads: 0, Total threads: 29
Worker threads: 23, Completion port threads: 0, Total threads: 30
Worker threads: 24, Completion port threads: 0, Total threads: 31
Worker threads: 25, Completion port threads: 0, Total threads: 32
Worker threads: 26, Completion port threads: 0, Total threads: 33
Worker threads: 27, Completion port threads: 0, Total threads: 34
Worker threads: 28, Completion port threads: 0, Total threads: 35
Worker threads: 29, Completion port threads: 0, Total threads: 36
========================================
Worker threads: 30, Completion port threads: 0, Total threads: 37线程池每秒旋转一个工作线程,而不是为完成端口线程启动的两个线程。显然,这些数字与实现有关,可能会在新版本中发生变化。
最后,让我们演示如何使用ThreadPool.SetMinThreads来确保可用最少数量的线程来完成请求。如果我们回到FileOptions.Asynchronous并将ThreadPool.SetMinThreads(50, 50)添加到我们的玩具程序的Main中,结果是:
Worker threads: 0, Completion port threads: 0, Total threads: 3
Worker threads: 0, Completion port threads: 31, Total threads: 35
========================================
Worker threads: 0, Completion port threads: 30, Total threads: 35现在,线程池不再耐心地每两秒钟添加一个线程,而是一直旋转线程直到达到最大值(在本例中不发生,因此最终计数保持在30)。当然,这30个线程都被困在无限等待中--但如果这是一个真正的系统,那么这30个线程现在可能会做一些有用的工作,如果不是非常有效的工作。不过,我不会用100000个请求来尝试这个。
发布于 2015-02-24 08:18:02
这是否意味着我将在这里同时运行1000个IOCP线程池线程,当所有线程都完成时?
没关系,您请。与ThreadPool中可用的工作线程一样,我们也有“完成端口线程”。
这些线程专门用于异步I/O。不会有预先创建的线程。它们是作为工作线程创建的随需应变( on demand,)。当线程池决定时,它们最终将被销毁。
借用作者的意思是将IO的完成通知进程,使用“完成端口线程”( ThreadPool)中的任意线程。它不会执行任何冗长的操作,而是完成IO通知。
https://stackoverflow.com/questions/28690815
复制相似问题