我一直在测试将IO完成端口与线程池中的工作线程结合起来,偶然发现了我无法解释的行为。特别是,以下代码:
int data;
for (int i = 0; i < NUM; ++i)
PostQueuedCompletionStatus(cp, 1, NULL, reinterpret_cast<LPOVERLAPPED>(&data));
{
std::thread t([&] ()
{
LPOVERLAPPED aux;
DWORD cmd;
ULONG_PTR key;
for (int i = 0; i < NUM; ++i)
{
if (!GetQueuedCompletionStatus(cp, &cmd, &key, &aux, 0))
break;
++count;
}
});
t.join();
}
完全正常并收到NUM状态通知(NUM为大数,100000或更多),类似的代码使用线程池工作对象读取每个工作项的一个状态通知,并在读取后重新发布工作项,阅读几百个状态通知后失败。拥有以下全局变量(请不要注意名称):
HANDLE cport;
PTP_POOL pool;
TP_CALLBACK_ENVIRON env;
PTP_WORK work;
std::size_t num_calls;
std::mutex mutex;
std::condition_variable cv;
bool job_done;
和回调函数:
static VOID CALLBACK callback(PTP_CALLBACK_INSTANCE instance_, PVOID pv_, PTP_WORK work_)
{
LPOVERLAPPED aux;
DWORD cmd;
ULONG_PTR key;
if (GetQueuedCompletionStatus(cport, &cmd, &key, &aux, 0))
{
++num_calls;
SubmitThreadpoolWork(work);
}
else
{
std::unique_lock<std::mutex> l(mutex);
std::cout << "No work after " << num_calls << " calls.\n";
job_done = true;
cv.notify_one();
}
}
以下代码:
{
job_done = false;
std::unique_lock<std::mutex> l(mutex);
num_calls = 0;
cport = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
pool = CreateThreadpool(nullptr);
InitializeThreadpoolEnvironment(&env);
SetThreadpoolCallbackPool(&env, pool);
work = CreateThreadpoolWork(callback, nullptr, &env);
for (int i = 0; i < NUM; ++i)
PostQueuedCompletionStatus(cport, 1, NULL, reinterpret_cast<LPOVERLAPPED>(&data));
SubmitThreadpoolWork(work);
cv.wait_for(l, std::chrono::milliseconds(10000), [] { return job_done; } );
}
报告&#34;在......之后再没有工作了。&#34;在250次左右调用GetQueuedCompletionStatus之后,尽管NUM设置为1000000.更奇怪的是,将等待从0设置为10毫秒会将成功调用的次数增加到几十万,并且偶尔会读取所有1000000个通知。由于所有状态通知都是在第一次提交工作对象之前发布的,因此我并不理解。
组合完成端口和线程池是否可能存在问题,或者我的代码中是否存在问题?请不要进入我为什么要这样做 - 我正在调查可能性并偶然发现了这一点。在我看来它应该有用,并且不能解决什么是错的。谢谢。
答案 0 :(得分:0)
我已尝试运行此代码,问题似乎是提供给NumberOfConcurrentThreads
的{{1}}参数。传递1表示执行CreateIoCompletionPort
的第一个池线程与io完成端口关联,但由于线程池可能使用不同的线程callback
执行callback
,因此会发生这种情况。 From documentation:
要仔细考虑的I / O完成端口最重要的属性是并发值。通过
GetQueuedCompletionStatus
参数使用CreateIoCompletionPort
创建完成端口时,将指定完成端口的并发值。此值限制与完成端口关联的可运行线程的数量。当与完成端口关联的可运行线程的总数达到并发值时,系统会阻止与该完成端口关联的任何后续线程的执行,直到可运行线程的数量降至并发值以下。虽然任意数量的线程可以为指定的I / O完成端口调用
NumberOfConcurrentThreads
,但是当指定的线程第一次调用GetQueuedCompletionStatus
时,它将与指定的I / O完成端口关联,直到发生以下三种情况之一:线程退出,指定不同的I / O完成端口,或关闭I / O完成端口。换句话说,单个线程最多可以与一个I / O完成端口相关联。
因此,要使用线程池完成io,您需要将并发线程数设置为线程池的大小(可以使用GetQueuedCompletionStatus
设置)。
SetThreadpoolThreadMaximum