Showing posts with label Windows. Show all posts
Showing posts with label Windows. Show all posts

Sunday, May 20, 2018

One of my favourite ways of multi-threading on Windows

Having blogged about a few bugs in multi-threading libraries recently, I want to show an easy and convenient alternative. It's minimalistic but it works.

On Windows, the I/O completion port offers a way to use a thread pool for your multi-threaded application. Designed primarily for efficient processing of asynchronous I/O, it supports files, named pipes, sockets and device control.

In addition to that, you can post your own packets to the port. Quoting from the documentation:
The PostQueuedCompletionStatus function allows an application to queue its own special-purpose completion packets to the I/O completion port without starting an asynchronous I/O operation.
An example of this is shown in the worker demo project (which uses no async I/O at all).

Sunday, March 27, 2016

The strange limitation of 64 threads

When using the Windows I/O Completion Port (IOCP), people seem to limit their thread pools to a maximum of 64 threads.

This is probably caused by the fact that WaitForMultipleObjects limits the number of input handles with the nice magic constant MAXIMUM_WAIT_OBJECTS (which happens to be 64).

Here are a few examples:

The (anti-)pattern is related to the process of shutting down the thread pool: to do this cleanly, the threads in the pool should be allowed to finish what they're doing (or just wake up if they're idle at the moment), perform any cleaning up as necessary and terminate correctly. The shutdown is usually performed in two steps:
  1. Send a shutdown signal (completion key) to each thread.
  2. Each thread in the pool calls GetQueuedCompletionStatus in a loop and checks for the special (application-defined) shutdown completion key to which it responds by breaking out of the loop and terminating. The shutdown procedure can therefore simply send the shutdown completion key to the IOCP as many times as there are threads, relying on the fact that exactly one thread will respond to exactly one such signal.
  3. Wait for all threads to terminate.
  4. The shutdown is not complete before all threads actually had a chance to receive the signal and terminate. Only then it's safe to continue closing the IOCP, freeing memory, etc. So we absolutely have to wait for the threads to terminate. The reasoning here seems to be: Since WaitForMultipleObjects can only handle up to 64 threads, we can't allow more threads to be associated with the pool in the first place, can we?

Well, there's no need to use WaitForMultipleObjects in Step 2. It's fairly easy to keep a counter of active threads in the pool (interlocked-incremented when a thread starts, interlocked-decremented when a thread is finished). When the counter reaches zero (no more active threads), signal an event. With only one event to wait for, you can use WaitForSingleObject in Step 2.