{"id":112223,"date":"2026-04-10T07:00:00","date_gmt":"2026-04-10T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112223"},"modified":"2026-04-10T14:32:07","modified_gmt":"2026-04-10T21:32:07","slug":"20260410-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260410-00\/?p=112223","title":{"rendered":"How do you add or remove a handle from an active <CODE>Wait&shy;For&shy;Multiple&shy;Objects<\/CODE>?, part 2"},"content":{"rendered":"<p>Last time, we looked at <a title=\"How do you add or remove a handle from an active Wait\u00adFor\u00adMultiple\u00adObjects?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260409-00\/?p=112220\"> adding or removing a handle from an active <code>Wait\u00adFor\u00adMultiple\u00adObjects<\/code><\/a>, and we developed an asynchronous mechanism that requests that the changes be made soon. But asynchronous add\/remove can be a problem bcause you might remove a handle, clean up the things that the handle was dependent upon, but then receive a notification that the handle you removed has been signaled, even though you already cleaned up the things the handle depended on.<\/p>\n<p>What we can do is wait for the waiting thread to acknowledge the operation.<\/p>\n<pre>_Guarded_by_(desiredMutex) DWORD desiredCounter = 1;\r\nDWORD activeCounter = 0;\r\n\r\nvoid wait_until_active(DWORD value)\r\n{\r\n    DWORD current = activeCounter;\r\n    while (static_cast&lt;int&gt;(current - value) &lt; 0) {\r\n        WaitOnAddress(&amp;activeCounter, &amp;current,\r\n                      sizeof(activeCounter), INFINITE);\r\n        current = activeCounter;\r\n    }\r\n}\r\n<\/pre>\n<p>The <code>wait_<wbr \/>until_<wbr \/>active<\/code> function waits until the value of <code>active\u00adCounter<\/code> is at least as large as <code>value<\/code>. We do this by subtracting the two values, to avoid wraparound problems.\u00b9 The comparison takes advantage of the guarantee in C++20 that conversion from an unsigned integer to a signed integer converts to the value that is numerically equal modulo 2\u207f where <var>n<\/var> is the number of bits in the destination. (Prior to C++20, the result was implementation-defined, but in practice all modern implementations did what C++20 mandates.)\u00b2<\/p>\n<p>You can also use <code>std::<wbr \/>atomic<\/code>:<\/p>\n<pre>_Guarded_by_(desiredMutex) DWORD desiredCounter = 1;\r\nstd::atomic&lt;DWORD&gt; activeCounter;\r\n\r\nvoid wait_until_active(DWORD value)\r\n{\r\n    DWORD current = activeCounter;\r\n    while (static_cast&lt;int&gt;(current - value) &lt; 0) {\r\n        activeCounter.wait(current);\r\n        current = activeCounter;\r\n    }\r\n}\r\n<\/pre>\n<p>As before, the background thread manipulates the <code>desiredHandles<\/code> and <code>desiredActions<\/code>, then signals the waiting thread to wake up and process the changes. But this time, the background thread blocks until the waiting thread acknowledges the changes.<\/p>\n<pre>\/\/ Warning: For expository purposes. Almost no error checking.\r\nvoid waiting_thread()\r\n{\r\n    bool update = true;\r\n    std::vector&lt;wil::unique_handle&gt; handles;\r\n    std::vector&lt;std::function&lt;void()&gt;&gt; actions;\r\n\r\n    while (true)\r\n    {\r\n        if (std::exchange(update, false)) {\r\n            std::lock_guard guard(desiredMutex);\r\n\r\n            handles.clear();\r\n            handles.reserve(desiredHandles.size() + 1);\r\n            std::transform(desiredHandles.begin(), desiredHandles.end(),\r\n                std::back_inserter(handles),\r\n                [](auto&amp;&amp; h) { return duplicate_handle(h.get()); });\r\n            \/\/ Add the bonus \"changed\" handle\r\n            handles.emplace_back(duplicate_handle(changed.get()));\r\n\r\n            actions = desiredActions;\r\n\r\n            <span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (activeCounter != desiredCounter) {<\/span>\r\n            <span style=\"border: 1px currentcolor; border-style: none solid;\">    activeCounter = desiredCounter;   <\/span>\r\n            <span style=\"border: solid 1px currentcolor; border-top: none;\">    WakeByAddressAll(&amp;activeCounter); <\/span>\r\n            }\r\n        }\r\n\r\n        auto count = static_cast&lt;DWORD&gt;(handles.size());\r\n                        \r\n        auto result = WaitForMultipleObjects(count,\r\n                        handles.data()-&gt;get(), FALSE, INFINITE);\r\n        auto index = result - WAIT_OBJECT_0;\r\n        if (index == count - 1) {\r\n            \/\/ the list changed. Loop back to update.\r\n            update = true;\r\n            continue;\r\n        } else if (index &lt; count - 1) {\r\n            actions[index]();\r\n        } else {\r\n            \/\/ deal with unexpected result\r\n        }\r\n    }\r\n}\r\n\r\nvoid change_handle_list()\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">DWORD value;<\/span>\r\n    {\r\n        std::lock_guard guard(desiredMutex);\r\n        \u27e6 make changes to desiredHandles and desiredActions \u27e7\r\n        <span style=\"border: solid 1px currentcolor;\">value = ++desiredCounter;<\/span>\r\n        SetEvent(changed.get());\r\n    }\r\n    <span style=\"border: solid 1px currentcolor;\">wait_until_active(value);<\/span>\r\n}\r\n<\/pre>\n<p>The pattern is that after the background thread makes the desired changes, they increment the <code>desiredCounter<\/code> and signal the event. It&#8217;s okay if multiple threads make changes before the waiting thread wakes up. The changes simply accumulate, and the event just stays signaled. Each background thread then waits for the waiting thread to process the change.<\/p>\n<p>On the waiting side, we process changes as usual, but we also publish our current change counter if it has changed, to let the background threads know that we made some progress. Eventually, we will make enough progress that all of the pending changes have been processed, and the last ackground thread will be released from <code>wait_<wbr \/>until_<wbr \/>active<\/code>.<\/p>\n<p>\u00b9 You&#8217;ll run into problems if the counter increments 2 billion times without the worker thread noticing. At a thousand increments per second, that&#8217;ll last you a month. I figure that if you have a worker thread that is unresponsible for that long, then you have bigger problems. But you can avoid even that problem by switching to a 64-bit integer, so that the overflow won&#8217;t happen before the sun is expected to turn into a red giant.<\/p>\n<p>\u00b2 The holdouts would be compilers for systems that are not two&#8217;s-complement.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Waiting for the waiting thread to acknowledge the change.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112223","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Waiting for the waiting thread to acknowledge the change.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112223"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112223\/revisions"}],"predecessor-version":[{"id":112224,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112223\/revisions\/112224"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}