为什么我们需要在工作线程退出时检查 IsIoPending?
Why we need to check the IsIoPending when the worker thread going to exit?
从win32threadpool.cpp我们知道,在工作线程通过检查20s超时退出之前,它需要通过IsIoPending()方法检查是否有任何IO挂起,根据我的理解:
1,当工作线程要退出时,它必须完成它的工作并且return到线程池。
2,根据上面的1,应该是没有IO pending的线程要退出了。
所以我的问题是为什么我们需要在线程即将死亡时检查 IO pending?或者,我们如何模拟上述情况发生?
RetryWaitForWork:
if (!WorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout))
{
if (!IsIoPending())
{
while (true)
{
RetryRetire:
DWORD result = RetiredWorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout, FALSE);
_ASSERTE(WAIT_OBJECT_0 == result || WAIT_TIMEOUT == result);
if (WAIT_OBJECT_0 == result)
{
foundWork = true;
counts = WorkerCounter.GetCleanCounts();
FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId());
goto Work;
}
if (!IsIoPending())
{
https://github.com/dotnet/coreclr/blob/master/src/vm/win32threadpool.cpp
如果你搜索 IsIoPending
(这是我遇到不熟悉的东西时首先要做的事情之一),你会看到更靠下一点的,它被称为它前面的以下评论:
// We can't exit a thread that has pending I/O - we'll "retire" it instead.
这几乎可以回答您的问题。为什么我们需要在允许它退出之前检查工作线程是否有任何 I/O 挂起?好吧,因为我们无法退出具有挂起 I/O.
的线程
唯一剩下的问题是,我想,为什么不?为什么我们不能退出具有挂起 I/O 的线程?为了对此进行调查,让我们看看 IsIoPending
实际上 做了什么 。进一步搜索文件,您会发现 its implementation:
// Returns true if there is pending io on the thread.
BOOL ThreadpoolMgr::IsIoPending()
{
CONTRACTL
{
NOTHROW;
MODE_ANY;
GC_NOTRIGGER;
}
CONTRACTL_END;
#ifndef FEATURE_PAL
int Status;
ULONG IsIoPending;
if (g_pufnNtQueryInformationThread)
{
Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(),
ThreadIsIoPending,
&IsIoPending,
sizeof(IsIoPending),
NULL);
if ((Status < 0) || IsIoPending)
return TRUE;
else
return FALSE;
}
return TRUE;
#else
return FALSE;
#endif // !FEATURE_PAL
}
这里的评论并没有告诉我们很多信息,除了确认该函数的名称是否正确以及它是否按照我们的想法行事!但是它的实现呢?
好吧,您注意到的第一件事是大多数有趣的东西都被 #ifndef FEATURE_PAL
条件测试封锁了。那么什么是FEATURE_PAL
呢? PAL代表Platform Adaptation Layer,它只是一种标记代码的方式只能在 Windows 上工作。如果定义了 FEATURE_PAL
,那么框架将针对 OS other 而不是 Windows 进行编译,因此 Windows 特定代码需要被排除在外。这正是您在这里看到的 — 当定义 FEATURE_FAL
时,此 IsIoPending
函数只是 returns FALSE
。只有当它 运行ning 在 Windows 之上时(当 FEATURE_PAL
是 未 定义时)才需要检查 I/O 是否是待办的。这非常强烈地表明上面关于无法退出具有挂起 I/O 的线程的评论是指 Windows 操作系统的规则。
如果我们 运行 在 Windows 上会发生什么?调用(间接地,通过全局函数指针)Windows API 函数 NtQueryInformationThread
。第一个参数传递当前线程的句柄(GetCurrentThread()
),第二个参数请求线程信息class ThreadIsIoPending
,接下来的两个参数允许函数填写ULONG
变量 IsIoPending
(他们传递它的大小和指向它的指针)。
如果您尝试阅读 NtQueryInformationThread
的文档,您会发现它是一个内部函数,建议应用程序:
Use the public function GetThreadIOPendingFlag
instead to obtain this information.
.NET 源代码不遵循此建议,因为该函数 (GetThreadIOPendingFlag
) 直到 Windows XP SP1 和 .NET 4(可能是旧版本,为此此代码是在 Windows 的下层版本上 运行 需要编写的)。所以他们只是调用了 Windows.
所有受支持版本上可用的内部函数
无论如何,GetThreadIOPendingFlag
的文档几乎证实了它做了我们所怀疑的事情:如果线程有任何 I/O 待处理请求,它 return 为真,或者否则为假。 .NET Framework 实现调用的内部函数将 return 相同的信息。
现在我想我们又回到了最初的问题:为什么一个线程是否有任何未决的 I/O 很重要?为什么我们在杀死它之前需要检查它?好吧,在 Windows 中,线程发出的 I/O 请求与该特定线程密不可分。无法将此 I/O 请求的所有权(或其结果数据)转移到另一个线程。换句话说,用户模式 IRPs 不能超过最初创建它们的线程。如果线程退出,所有pending的I/O都会被毫不客气的取消,永远不会完成。因此,我们得到了原始评论中如此简洁地陈述的规则:如果一个线程有待处理的 I/O,它就不能退出(因为那样 I/O 将永远不会完成,并且会永远丢失)。
GetThreadIOPendingFlag
函数(或 NtQueryInformationThread
和 class ThreadIsIoPending
)只是检查指定线程的活动 IRP 列表是否为空。如果没有 I/O 请求处于待处理状态,则退出线程是安全的。
工作线程可能有待处理的 I/O 请求的原因有很多,但最常见的情况是线程发出了 overlapped (asynchronous) I/O request。在这种情况下,超时可能会在发出 I/O 完成信号之前结束。异步 I/O 依赖于发布线程是 Win32 体系结构的一个基本限制,线程池的 .NET Framework 实现意识到了这一限制并解决了这一问题。
此检查 通常 return 可能是错误的,但为了安全起见,明确检查它是个好主意。这些是标准的防御性编程实践——对于在全球范围内发售的框架而言,这是一个非常重要的实践,运行 处于各种不同的条件下,并且需要尽可能稳健。
从win32threadpool.cpp我们知道,在工作线程通过检查20s超时退出之前,它需要通过IsIoPending()方法检查是否有任何IO挂起,根据我的理解:
1,当工作线程要退出时,它必须完成它的工作并且return到线程池。
2,根据上面的1,应该是没有IO pending的线程要退出了。
所以我的问题是为什么我们需要在线程即将死亡时检查 IO pending?或者,我们如何模拟上述情况发生?
RetryWaitForWork:
if (!WorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout))
{
if (!IsIoPending())
{
while (true)
{
RetryRetire:
DWORD result = RetiredWorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout, FALSE);
_ASSERTE(WAIT_OBJECT_0 == result || WAIT_TIMEOUT == result);
if (WAIT_OBJECT_0 == result)
{
foundWork = true;
counts = WorkerCounter.GetCleanCounts();
FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId());
goto Work;
}
if (!IsIoPending())
{
https://github.com/dotnet/coreclr/blob/master/src/vm/win32threadpool.cpp
如果你搜索 IsIoPending
(这是我遇到不熟悉的东西时首先要做的事情之一),你会看到更靠下一点的,它被称为它前面的以下评论:
// We can't exit a thread that has pending I/O - we'll "retire" it instead.
这几乎可以回答您的问题。为什么我们需要在允许它退出之前检查工作线程是否有任何 I/O 挂起?好吧,因为我们无法退出具有挂起 I/O.
的线程唯一剩下的问题是,我想,为什么不?为什么我们不能退出具有挂起 I/O 的线程?为了对此进行调查,让我们看看 IsIoPending
实际上 做了什么 。进一步搜索文件,您会发现 its implementation:
// Returns true if there is pending io on the thread.
BOOL ThreadpoolMgr::IsIoPending()
{
CONTRACTL
{
NOTHROW;
MODE_ANY;
GC_NOTRIGGER;
}
CONTRACTL_END;
#ifndef FEATURE_PAL
int Status;
ULONG IsIoPending;
if (g_pufnNtQueryInformationThread)
{
Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(),
ThreadIsIoPending,
&IsIoPending,
sizeof(IsIoPending),
NULL);
if ((Status < 0) || IsIoPending)
return TRUE;
else
return FALSE;
}
return TRUE;
#else
return FALSE;
#endif // !FEATURE_PAL
}
这里的评论并没有告诉我们很多信息,除了确认该函数的名称是否正确以及它是否按照我们的想法行事!但是它的实现呢?
好吧,您注意到的第一件事是大多数有趣的东西都被 #ifndef FEATURE_PAL
条件测试封锁了。那么什么是FEATURE_PAL
呢? PAL代表Platform Adaptation Layer,它只是一种标记代码的方式只能在 Windows 上工作。如果定义了 FEATURE_PAL
,那么框架将针对 OS other 而不是 Windows 进行编译,因此 Windows 特定代码需要被排除在外。这正是您在这里看到的 — 当定义 FEATURE_FAL
时,此 IsIoPending
函数只是 returns FALSE
。只有当它 运行ning 在 Windows 之上时(当 FEATURE_PAL
是 未 定义时)才需要检查 I/O 是否是待办的。这非常强烈地表明上面关于无法退出具有挂起 I/O 的线程的评论是指 Windows 操作系统的规则。
如果我们 运行 在 Windows 上会发生什么?调用(间接地,通过全局函数指针)Windows API 函数 NtQueryInformationThread
。第一个参数传递当前线程的句柄(GetCurrentThread()
),第二个参数请求线程信息class ThreadIsIoPending
,接下来的两个参数允许函数填写ULONG
变量 IsIoPending
(他们传递它的大小和指向它的指针)。
如果您尝试阅读 NtQueryInformationThread
的文档,您会发现它是一个内部函数,建议应用程序:
Use the public function
GetThreadIOPendingFlag
instead to obtain this information.
.NET 源代码不遵循此建议,因为该函数 (GetThreadIOPendingFlag
) 直到 Windows XP SP1 和 .NET 4(可能是旧版本,为此此代码是在 Windows 的下层版本上 运行 需要编写的)。所以他们只是调用了 Windows.
无论如何,GetThreadIOPendingFlag
的文档几乎证实了它做了我们所怀疑的事情:如果线程有任何 I/O 待处理请求,它 return 为真,或者否则为假。 .NET Framework 实现调用的内部函数将 return 相同的信息。
现在我想我们又回到了最初的问题:为什么一个线程是否有任何未决的 I/O 很重要?为什么我们在杀死它之前需要检查它?好吧,在 Windows 中,线程发出的 I/O 请求与该特定线程密不可分。无法将此 I/O 请求的所有权(或其结果数据)转移到另一个线程。换句话说,用户模式 IRPs 不能超过最初创建它们的线程。如果线程退出,所有pending的I/O都会被毫不客气的取消,永远不会完成。因此,我们得到了原始评论中如此简洁地陈述的规则:如果一个线程有待处理的 I/O,它就不能退出(因为那样 I/O 将永远不会完成,并且会永远丢失)。
GetThreadIOPendingFlag
函数(或 NtQueryInformationThread
和 class ThreadIsIoPending
)只是检查指定线程的活动 IRP 列表是否为空。如果没有 I/O 请求处于待处理状态,则退出线程是安全的。
工作线程可能有待处理的 I/O 请求的原因有很多,但最常见的情况是线程发出了 overlapped (asynchronous) I/O request。在这种情况下,超时可能会在发出 I/O 完成信号之前结束。异步 I/O 依赖于发布线程是 Win32 体系结构的一个基本限制,线程池的 .NET Framework 实现意识到了这一限制并解决了这一问题。
此检查 通常 return 可能是错误的,但为了安全起见,明确检查它是个好主意。这些是标准的防御性编程实践——对于在全球范围内发售的框架而言,这是一个非常重要的实践,运行 处于各种不同的条件下,并且需要尽可能稳健。