从线程池工作线程使用时,GetQueuedCompletionStatus的奇怪行为

时间:2017-08-08 08:43:31

标签: c++ windows winapi threadpool

我一直在测试将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个通知。由于所有状态通知都是在第一次提交工作对象之前发布的,因此我并不理解。

组合完成端口和线程池是否可能存在问题,或者我的代码中是否存在问题?请不要进入我为什么要这样做 - 我正在调查可能性并偶然发现了这一点。在我看来它应该有用,并且不能解决什么是错的。谢谢。

1 个答案:

答案 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