{"id":109887,"date":"2024-06-12T07:00:00","date_gmt":"2024-06-12T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109887"},"modified":"2024-06-13T11:49:38","modified_gmt":"2024-06-13T18:49:38","slug":"20240612-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240612-00\/?p=109887","title":{"rendered":"Lock-free reference-counting a TLS slot using atomics, part 1"},"content":{"rendered":"<p>Some time ago, we spent time looking at various lock-free algorithms, one of which is the <a title=\"Lock-free algorithms: The singleton constructor\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110406-00\/?p=11023\"> lock-free singleton constructor<\/a>. But suppose you want your singleton to be reference-counted?<\/p>\n<p>To make things concrete, let&#8217;s suppose that we want a class which manages a TLS slot, allocating it on demand, and freeing it when there are no longer any users.<\/p>\n<p>Let&#8217;s start with a sketch of how we want this to work, but without worrying about atomicity yet.<\/p>\n<pre>\/\/ Note: Not finished yet\r\nstruct TlsManager\r\n{\r\n    DWORD m_count = 0;\r\n    DWORD m_tls = TLS_OUT_OF_INDEXES;\r\n\r\n    void Acquire()\r\n    {\r\n        if (++m_count == 1) {\r\n            m_tls = TlsAlloc();\r\n            THROW_LAST_ERROR_IF(m_tls == TLS_OUT_OF_INDEXES);\r\n        }\r\n    }\r\n\r\n    void Release()\r\n    {\r\n        if (--m_count == 0) {\r\n            TlsFree(std::exchange(m_tls, TLS_OUT_OF_INDEXES));\r\n        }\r\n    }\r\n};\r\n\r\nstruct TlsUsage\r\n{\r\n    TlsUsage() = default;\r\n\r\n    explicit TlsUsage(TlsManager&amp; manager) :\r\n        m_manager(&amp;manager) { manager.Acquire(); }\r\n\r\n    TlsUsage(TlsUsage&amp;&amp; other) :\r\n        m_manager(std::exchange(other.manager, nullptr)) {}\r\n\r\n    TlsUsage&amp; operator=(TlsUsage&amp;&amp; other) {\r\n        std::swap(m_manager, other.m_manager);\r\n    }\r\n\r\n    ~TlsUsage()\r\n    {\r\n        if (m_manager) m_manager-&gt;Release();\r\n    }\r\n\r\n    void* GetValue()\r\n    {\r\n        return TlsGetValue(m_manager-&gt;m_tls);\r\n    }\r\n\r\n    void SetValue(void* value)\r\n    {\r\n        TlsSetValue(m_manager-&gt;m_tls, value);\r\n    }\r\n\r\n    TlsManager* m_manager = nullptr;\r\n};\r\n<\/pre>\n<p>The idea here is that a <code>Tls\u00adManager<\/code> is the object that manages access to a TLS slot. You call <code>Acquire<\/code> to start using the TLS slot (allocating it on demand), and you can use that slot until you call <code>Release<\/code>. When the last consumer of a slot calls <code>Release<\/code>, the slot is freed.<\/p>\n<p>Instead of talking directly to the <code>Tls\u00adManager<\/code>, you use a <code>Tls\u00adUsage<\/code>, which is an RAII type that deals with the acquire\/release protocol for you.<\/p>\n<p>To make the <code>Tls\u00adManager<\/code> thread-safe, we can add locks:<\/p>\n<pre>struct TlsManager\r\n{\r\n    DWORD m_count = 0;\r\n    DWORD m_tls = TLS_OUT_OF_INDEXES;\r\n    std::mutex m_mutex;\r\n\r\n    void Acquire()\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">auto lock = std::unique_lock(m_mutex);<\/span>\r\n\r\n        if (++m_count == 1) {\r\n            m_tls = TlsAlloc();\r\n            THROW_LAST_ERROR_IF(m_tls == TLS_OUT_OF_INDEXES);\r\n        }\r\n    }\r\n\r\n    void Release()\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">auto lock = std::unique_lock(m_mutex);<\/span>\r\n\r\n        if (--m_count == 0) {\r\n            TlsFree(std::exchange(m_tls, TLS_OUT_OF_INDEXES));\r\n        }\r\n    }\r\n};\r\n<\/pre>\n<p>Now, in practice, this might end up being efficient enough if <code>Tls\u00adUsage<\/code> objects are not frequently created and destroyed. But you might be in a case where your program is constantly creating and destroying <code>Widget<\/code> objects, and each <code>Widget<\/code> needs a <code>Tls\u00adUsage<\/code>. That lock might end up being a bottleneck. We&#8217;ll try to address this next time.<\/p>\n<p><b>Update<\/b>: <code>TlsUsage<\/code> move constructor and assignment fixed.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>First, we do it with locks.<\/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-109887","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>First, we do it with locks.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109887","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=109887"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109887\/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=109887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}