Increasing MAXIMUM_WAIT_OBJECTS for WaitforMultipleObjects
Asked Answered
F

4

5

What is the simplest way to wait for more objects than MAXIMUM_WAIT_OBJECTS? MSDN lists this:

  • Create a thread to wait on MAXIMUM_WAIT_OBJECTS handles, then wait on that thread plus the other handles. Use this technique to break the handles into groups of MAXIMUM_WAIT_OBJECTS.
  • Call RegisterWaitForSingleObject to wait on each handle. A wait thread from the thread pool waits on MAXIMUM_WAIT_OBJECTS registered objects and assigns a worker thread after the object is signaled or the time-out interval expires.

But neither are them are very clear. The situation would be waiting for an array of over a thousand handles to threads.

Fervent answered 27/2, 2011 at 23:19 Comment(7)
am i seeing this q over and over again?Swayne
If you detect a cross impedance mismatch between 64 and 1000+ then you're on to something. The api only really supports sane usage of operating system resources.Cretinism
No, @Aaa, you aren't. You simply saw a related question where Jake expressed disbelief that the limit was 64. This one is the follow-up question asking what he can do about it.Sabinesabino
IMHO, If you need to wait for over a thousand of threads, something is seariously wrong with your design. More threads do not mean more speed. I bet the work done by 1000 threads could easily be handled by at most 5-10 threads much more efficiently. Also like @Hans said your are wasting 990 * default stack size of memory, which may lead to Out of Memory.Elisa
@HansPassant: What is "sane" is highly debatable. To give an example, if you create a sub-process, you need to watch 4 handles for std in/out/err plus the process. Which means you cannot launch more than 16 processes due to API limitations. Launching 16 sub-processes may seem like a lot, but it isn't in a time where commodity PCs already have nearly that many hardware threads, tens of gigabytes of memory, and close to 600MiB/s I/O bandwidth. High performance systems surpass these numbers. Launching 16+ processes is something that e.g. an IDE might very well want to do.Baluster
The solution offered by Windows is "use more threads", which admittedly a rather poor solution. What's wrong with allowing a wait operation to wait on 500 or 1000 handles?Baluster
@ali_bahoo: it wastes only address space, which isn't a problem on 64-bit machines. The stacks aren't committed all at once.Mob
S
8

If you find yourself waiting on tons of objects you might want to look into IO Completion Ports instead. For large numbers of parallel operations IOCP is much more efficient.

And the name IOCP is misleading, you can easily use IOCP for your own synchronization structures as well.

Stumer answered 28/2, 2011 at 7:50 Comment(0)
R
3

I encountered this limitation in WaitForMultipleObjects myself and came to the conclusion I had three alternatives:

  • OPTION 1. Change the code to create separate threads to invoke WaitForMultipleObjects in batches less than MAXIMUM_WAIT_OBJECTS. I decided against this option, because if there are already 64+ threads fighting for the same resource, I wanted to avoid creating yet more threads if possible.
  • OPTION 2. Re-implement the code using a different technique (IOCP, for example). I decided against this too because the codebase I am working on is tried, tested and stable. Also, I have better things to do!
  • OPTION 3. Implement a function that splits the objects into batches less than MAXIMUM_WAIT_OBJECTS, and call WaitForMultipleObjects repeatedly in the same thread.

So, having chosen option 3 - here is the code I ended up implementing ...

class CtntThread
{
    public: 
        static DWORD WaitForMultipleObjects( DWORD, const HANDLE*, DWORD millisecs );
};

DWORD CtntThread::WaitForMultipleObjects( DWORD count, const HANDLE *pHandles, DWORD millisecs )
{
    DWORD retval = WAIT_TIMEOUT;

    // Check if objects need to be split up. In theory, the maximum is
    // MAXIMUM_WAIT_OBJECTS, but I found this code performs slightly faster
    // if the object are broken down in batches smaller than this.
    if ( count > 25 )
    {
        // loop continuously if infinite timeout specified
        do
        {
            // divide the batch of handles in two halves ...
            DWORD split = count / 2;
            DWORD wait = ( millisecs == INFINITE ? 2000 : millisecs ) / 2;
            int random = rand( );

            // ... and recurse down both branches in pseudo random order
            for ( short branch = 0; branch < 2 && retval == WAIT_TIMEOUT; branch++ )
            {
                if ( random%2 == branch ) 
                {
                    // recurse the lower half
                    retval = CtntThread::WaitForMultipleObjects( split, pHandles, wait );
                }
                else
                {
                    // recurse the upper half
                    retval = CtntThread::WaitForMultipleObjects( count-split, pHandles+split, wait );
                    if ( retval >= WAIT_OBJECT_0 && retval < WAIT_OBJECT_0+split ) retval += split;
                }
            }
        }
        while ( millisecs == INFINITE && retval == WAIT_TIMEOUT );
    }
    else
    {
        // call the native win32 interface
        retval = ::WaitForMultipleObjects( count, pHandles, FALSE, millisecs );
    }

    // done
    return ( retval );
}
Roundup answered 16/8, 2012 at 14:17 Comment(1)
Does this actually work? As far as I can see, you could be waiting for several seconds while non-signalled WaitForMultipleObjects() calls time out before you get to the one with the signalled handle. The function is supposed to return immediately any handle is signalled.Zolnay
F
1

Have a look here.

If you need to wait on more than MAXIMUM_WAIT_OBJECTS handles, you can either create a separate thread to wait on MAXIMUM_WAIT_OBJECTS and then do a wait on these threads to finish. Using this method you can create MAXIMUM_WAIT_OBJECTS threads each of those can wait for MAXIMUM_WAIT_OBJECTS object handles.

Fresher answered 27/2, 2011 at 23:32 Comment(2)
If you do use the example code, my suggestion would be to specify a non-default stack space size in CreateThread (say "1", which should round up to a system page in size). No sense reserving the presumed default 1 MiB of address space per thread when almost none of it will be used.Kneehigh
What's the novel idea here? It's just repeating what's spelled out in the question already. What am I missing?Pishogue
R
0

Since Windows 8 there's a third way: You can use NT API to get IOCP completion packet when an event, semaphore or a thread handle gets signalled (thread finishes).

To do that you create this completion packet with NtCreateWaitCompletionPacket , and bind all three handles (this packet, IOCP and thread/event) together through poorly documented (but intuitive enough) NtAssociateWaitCompletionPacket. When you receive the completion, you can call it again to reestablish the association and receive the next one, when the object is signalled again.

See full example here: https://github.com/tringi/win32-iocp-events

Redistrict answered 24/8, 2024 at 20:45 Comment(5)
Please don't link to external resources. Repeat the core parts here (assuming that the license allows this).Pishogue
Alright @Pishogue - I've added a description, or do you mean copying in a full example code?Desdee
That's better, though the link to the Git repository is of little use seeing as it doesn't come with a license.Pishogue
Do these trivial examples really need licenses? The implementation is effectively one function that calls another. And the usage example is something you'd find in API documentation.Desdee
When you publish anything you continue to hold the exclusive copyright of the content. If you want to allow others to copy the code, no matter how trivial, you'll have to explicitly document that. That's what a "software license" (among other things) is for.Pishogue

© 2022 - 2025 — McMap. All rights reserved.