{"id":106434,"date":"2022-04-06T07:00:00","date_gmt":"2022-04-06T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=106434"},"modified":"2022-04-02T05:36:17","modified_gmt":"2022-04-02T12:36:17","slug":"20220406-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20220406-00\/?p=106434","title":{"rendered":"All Windows threadpool waits can now be handled by a single thread"},"content":{"rendered":"<p>I noted some time ago that <a title=\"Why bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20081117-00\/?p=20183\"> creating a threadpool wait allows the threadpool to combine multiple waits<\/a>, so that each thread waits for nearly 64 objects. (It&#8217;s not quite 64 objects because one of the objects is a special sentinel object that means &#8220;Stop waiting.&#8221;)<\/p>\n<p>In the time since I wrote that article, the situation has gotten even better. Starting in Windows 8, the registered waits are associated with a completion port, and a single thread handles all the wait requests by waiting for completions.<\/p>\n<p>We can see the new behavior in action with this simple program:<\/p>\n<pre>#include &lt;windows.h&gt;\r\n#include &lt;stdio.h&gt;\r\n\r\nint main()\r\n{\r\n    static LONG count = 0;\r\n    HANDLE last = CreateEvent(nullptr, true, false, nullptr);\r\n\r\n    HANDLE event = last;\r\n    for (int i = 0; i &lt; 10000; i++)\r\n    {\r\n        auto wait = CreateThreadpoolWait(\r\n        [](auto, auto event, auto, auto)\r\n        {\r\n            InterlockedIncrement(&amp;count);\r\n            SetEvent(event);\r\n        }, event, nullptr);\r\n        event = CreateEvent(nullptr, true, false, nullptr);\r\n        SetThreadpoolWait(wait, event, nullptr);\r\n    }\r\n\r\n    Sleep(10000);\r\n    SetEvent(event);\r\n    WaitForSingleObject(last, INFINITE);\r\n    printf(\"%d events signaled\\n\", count);\r\n    return 0;\r\n}\r\n<\/pre>\n<p>This quick-and-dirty program creates 10,000 threadpool waits, each waiting on a different event, and whose callback signals the next event, creating a chain of waits that eventually lead to setting the event named <code>last<\/code>. Under the old rules, creating 10,000 threadpool waits would result in around 10,000 \u00f7 63 \u2245 232 threads to wait on all of those objects. But if you break into the debugger during the <code>Sleep()<\/code>, you&#8217;ll see that there are just a few. And if you set a breakpoint at the start of the <code>main<\/code> function, you&#8217;ll see that only one of those threads was created as a result of the threadpool waits; the others were pre-existing.<\/p>\n<p>To prove that all of these waits really are waiting, we signal the most recent one, which sets off a chain of <code>SetEvent<\/code> calls, and wait for the last event to be set. We print the number of events that were signaled (should be 10,000) and call it a day.<\/p>\n<p>This is just a proof of concept to show the thread behavior, so I didn&#8217;t bother cleaning up the waits or the handles.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Come in and join the thread, the water&#8217;s fine.<\/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-106434","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Come in and join the thread, the water&#8217;s fine.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106434","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=106434"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/106434\/revisions"}],"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=106434"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=106434"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=106434"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}