If more than one object causes a WaitForMultipleObjects to return, how do I find out about the other ones?


There is often confusion about the case where you call Wait­For­Multiple­Objects (or its variants), passing bWaitAll = FALSE, and more than one object satisfies the wait.

When bWaitAll is FALSE, this function checks the handles in the array in order starting with index 0, until one of the objects is signaled. If multiple objects become signaled, the function returns the index of the first handle in the array whose object was signaled.

The function modifies the state of some types of synchronization objects. Modification occurs only for the object or objects whose signaled state caused the function to return.

The worry here is that you pass bWaitAll = FALSE, and two objects satisfy the wait, and they are both (say) auto-reset events.

The first paragraph says that the return value tells you about the event with the lowest index. The second paragraph says that the function modifies the state of the "object or objects" whose signaled state caused the function to return.

The "or objects" part is what scares people. If two objects caused the function to return, and I am told only about one of them, how do I learn about the other one?

The answer is, "Don't worry; it can't happen."

If you pass bWaitAll = FALSE, then only one object can cause the function to return. If two objects become signaled, then the function declares that the lowest-index one is the one that caused the function to return; the higher-index ones are considered not have have caused the function to return.

In the case of the two auto-reset events: If both events are set, one of them will be chosen as the one that satisfies the wait (the lower-index one), and it will be reset. The higher-index one remains unchanged.

The confusion stems from the phrase "object or objects", causing people to worry about the case where bWaitAll = FALSE and there are multiple objects which cause the function to return.

The missing detail is that when you pass bWaitAll = FALSE, then at most one object can cause the function to return. ("At most", because if the operation times out, then no object caused the function to return.)

The presence of the phrase "or objects" is to cover the case where you pass bWaitAll = TRUE.

Comments (23)
  1. dave says:

    I can't think of a common use-case where the setting of bWaitAll would be computed at run-time.  It therefore seems like two functions would have been better than one.

    h71000.www7.hp.com/…/4527pro_121.html

  2. Mantas says:

    …surely you aren't suggesting that there should be individual functions every time a function parameter is a constant?

    (The open() mode, for example, is also never computed at run-time, and yet there is open(…, O_WRONLY|O_APPEND) instead of open_read() vs open_write_append() vs open_write_truncate() vs open_write_create_or_truncate() &c. just because one would end up having too many combinations.)

    [The explosion propagates, because you would also need two versions of Wait­For­Multiple­Objects­Ex, two versions of Msg­Wait­For­Multiple­Objects, two versions of Msg­Wait­For­Multiple­Objects­Ex, two versions of Co­Wait­For­Multiple­Handles, two versions of Co­Wait­For­Multiple­Objects, …]
  3. Adam Rosenfield says:

    This seems like the only sensible way to design WaitForMultipleObjects.  If you can only return one meaningful piece of information (the first handle which was signaled), then you should only modify the state (auto-reset the event) of objects referenced by that information.

    Conversely, the select(2) POSIX system call returns multiple pieces of information (the set of all FDs which are readable/writeable/exceptional); if it were to modify the state of any of those FDs, the calling code could know all of the FDs modified and act on them accordingly.  It doesn't actually modify the FDs, but you could imagine an alternate universe in which it did.

  4. Eric Sampson says:

    What about a doc enhancement request to change the text to something along the following lines, for clarity?

    "Modification occurs only for the object, or objects (only if bWaitAll = true), whose signaled state caused the function to return."

  5. 640k says:

    What about bringing back the msdn wiki? (with a better wiki engine than last time) Then it would have already been fixed.

  6. Joshua says:

    > The open() mode, for example, is also never computed at run-time

    I beg to differ.

    > returns multiple pieces of information

    This prevents resource starvation, but I've never seen it be a real issue.

    > It doesn't actually modify the FDs, but you could imagine an alternate universe in which it did.

    Good imagination. If we ever added auto-reset events to kernel it would.

  7. Nawak says:

    There are no WaitForAllObjects/WaitForAnyObjects, yet there is a WaitForSingleObject which can also be seen as a special case of WaitForMultipleObjects… but maybe there's an historical reason explaining why reducing the number of functions didn't apply in this case?

    [You may have noticed that none of the higher level functions picked up on the simplification. There is no CoWaitForSingleObject or MsgWaitForSingleObject. -Raymond]
  8. Joshua says:

    @Nawak: WaitForSingleObject doesn't require passing an array to kernel. All variants would.

  9. dave says:

    No, I'm arguing that "wait for any one object" and "wait for all of them" are sufficiently different as to benefit from being different; there's little point in combining them.

    Then, for example, waitForAnyObject could return the handle of the signalled object, and waitForAllObjects could return a value that doesn't look like it's a reference to only the first one (WAIT_OBJECT_0).  Even if it's only an alias for the same bit-pattern.

    As a bonus, we'd get separate documentation for separate semantics.

  10. Ben Voigt says:

    Better than the "object or objects" and covers the interaction with bWaitAll:

    "If bWaitAll is FALSE, modification occurs only for the object whose signaled state caused the function to return."

    It's not necessary for this sentence to apply with bWaitAll is TRUE, because then modification doesn't apply to a subset, but to all handles in the array.

  11. Kevin says:

    @Ben: Then people would think it resets the events which haven't even been signaled yet.

  12. mh says:

    I can think of one case where this might be useful.

    Say you have a set of jobs to assign to a thread pool, and you want to wait until one or more threads in the pool become available, then assign as many jobs as you have available threads.  Knowing that more than one thread is available (and which threads they are) would be useful in that case.  Of course you could also achieve this by calling WaitForMultipleObjects in a loop, assigning one job per iteration, but I'm wondering if there may be more efficiency in being able to do multiple assignments per WaitForMultipleObjects call.

    All hypothetical of course, and the internals of WaitForMultipleObjects might make this a complete non-issue.

  13. Nawak says:

    @Joshua

    In C, the address of a variable is an array of size 1, so the call wouldn't be that more complex:

    WFSO(myHandle, timeout) => WFMO(1, &myHandle, TRUE, timeout)

    @Raymond

    I didn't notice that because I don't really know the higher level functions! I barely even know Win32! My question was quite candid, I supposed that dave's point (two WFMO instead of one) was considered at the time and that there was a good reason for not splitting the "any object"/"all objects" cases but still splitting out the "one object" case…

    Knowing now that the higher level functions didn't split this case, I imagine that the decision made for Win32 was later regretted…

    Because I love speculation ;-), here are the possible explanations I see for the decision of having separate WFSO and WFMO:

    – a design mistake (hindsight always 20/20)

    – design's "best practices" evolved over time

    – WFSO was created first, at a time when waiting for multiple objects wasn't possible for the windows kernel, and then when it became possible, WFMO was added and head were banged.

    I think the third one would fit quite well with the theme of this blog! :)

  14. Ray Trent says:

    It seems to me, then, that the simplest and clearest answer to "how do I learn about the other one? " would be "Call Wait­For­Multiple­Objects again.".

    Is that accurate?

  15. Sven2 says:

    @Nawak "WFSO was created first, at a time when waiting for multiple objects wasn't possible for the windows kernel, and then when it became possible, WFMO was added and head were banged."

    Then why was it called "WaitForSingleObject" and not just "WaitForObject"? They must at least have had the possibility for WFMO in mind.

  16. Sven2 says:

    @Ray Trent: Or you could just check if they are signaled.

  17. dave says:

    @Nawak

    I assume the decision was deliberate, since the example I linked to earlier ($WFLOR, $WFLAND) came from VMS, which Dave Cutler (design lead for NT) had also designed.

    The overall trend in the NT native API is "honking great functions with many parameters".

  18. Joshua says:

    @Nawek: But that requires passing by address into kernel mode. WaitForSingleObject can pass by register, which is far cheaper.

  19. voo says:

    @Joshua you're in the process of making a kernel call. The overhead of one indirection to the l1 cache is immeasurable.

  20. Mark says:

    I think another important piece to the puzzle of confusion is the last part of "If multiple objects become signaled, the function returns the index of the first handle in the array whose object was signaled." This currently implies that when the function returns the (say) auto-reset objects will no longer be signalled. I'd say "whose object is in a signal state" instead.

  21. Ariel says:

    @voo

    Getting a value from userspace requires a get_user or copy_from_user, which is more expensive than a simple memory access.

  22. Nawak says:

    @Sven2: D'oh! You're right about that!

    So as @Dave points out, the API was designed like that. The design patterns evolved over time so that when higher level functions were designed, the fashion was having less functions (but more generic) instead of having different functions for special cases.

  23. Nawak says:

    @Ariel: is it? Shouldn't copy_from_user be only noticeably more expensive than memcpy when the passed pointer is invalid? How does it compare to the cost of making a system call?

Comments are closed.

Skip to main content