{"id":431,"date":"2016-03-17T15:32:46","date_gmt":"2016-03-17T15:32:46","guid":{"rendered":"http:\/\/www.crazygaze.com\/blog\/?p=431"},"modified":"2016-08-24T15:49:04","modified_gmt":"2016-08-24T15:49:04","slug":"how-strands-work-and-why-you-should-use-them","status":"publish","type":"post","link":"https:\/\/www.crazygaze.com\/blog\/2016\/03\/17\/how-strands-work-and-why-you-should-use-them\/","title":{"rendered":"How strands work and why you should use them"},"content":{"rendered":"<p>If you ever used Boost Asio, certainly you used or at least looked at <a href=\"http:\/\/www.boost.org\/doc\/libs\/1_60_0\/doc\/html\/boost_asio\/overview\/core\/strands.html\">strands<\/a> .<\/p>\n<p>The main benefit of using strands is to simplify your code, since handlers that go through a strand don&#8217;t need explicit synchronization. A strand guarantees that no two handlers execute concurrently.<\/p>\n<p>If you use just one IO thread (or in Boost terms, just one thread calling <em>io_service::run<\/em>), then you don&#8217;t need synchronization anyway. That is already an implicit strand.<br \/>\nBut the moment you want to ramp up and have more IO threads, you need to either deal with explicit synchronization for your handlers, or use strands.<\/p>\n<p>Explicitly synchronizing your handlers is certainly possible, but you will be unnecessarily introducing complexity to your code which will certainly lead to bugs. One other effect of explicit handler synchronization is that unless you think really hard, you&#8217;ll very likely introduce unnecessary blocking.<\/p>\n<p>Strands work by introducing another layer between your application code and the handler execution. Instead of having the worker threads directly execute your handlers, those handlers are queued in a strand. The strand then has control over when executing the handlers so that all guarantees can be met.<\/p>\n<p>One way you can think about is like this:<\/p>\n<p><img alt='' class='alignnone size-full wp-image-468 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56eab6e68d66d.png' \/><\/p>\n<h4>Possible scenario<\/h4>\n<p>To visually demonstrate what happens with the IO threads and handlers, I&#8217;ve used <a href=\"https:\/\/github.com\/Celtoys\/Remotery\">Remotery<\/a> .<\/p>\n<p>The code used emulates 4 worker threads, and 8 connections. Handlers (aka <em>work items<\/em>) with a random workload of <code>[5ms,15ms]<\/code> for a random connection are placed in the worker queue. In practice, you would not have this threads\/connections ratio or handler workload, but it makes it easier to demonstrate the problem. Also, I&#8217;m not using Boost Asio at all. It&#8217;s a custom strand implementation to explore the topic.<\/p>\n<p>So, a view into the worker threads:<\/p>\n<p><img alt='' class='alignnone size-full wp-image-445 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e92eeeb1e5b.png' \/><\/p>\n<p><em>Conn N<\/em> are our connection objects (or any object doing work in our worker threads for that matter). Each has a distinct colour. All good on the surface as you can see. Now lets look at what each <em>Conn<\/em> object is actually doing with its time slice.<\/p>\n<p><img alt='' class='alignnone size-full wp-image-446 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e932f2e8c6a.png' \/><\/p>\n<p>What is happening is the worker queue and worker threads are oblivious to what its handlers do (as expected), so the worker threads will happily dequeue work as it comes. One thread tries to execute work for a given <em>Conn<\/em> object which is already being used in another worker thread, so it has to block.<br \/>\nIn this scenario, <em>~19%<\/em> of total time is wasted with blocking or other overhead. In other words, only <em>~81%<\/em> of the worker thread&#8217;s time is spent doing actual work:<\/p>\n<p><img alt='' class='alignnone size-full wp-image-447 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e934f573b81.png' \/><\/p>\n<p>NOTE: The overhead was measured by subtracting actual work time from the total worker thread&#8217;s time. So it accounts for explicit synchronization in the handlers and any work\/synchronization done internally by the worker queue\/threads.<\/p>\n<p>Lets see how it looks like if we use strands to serialize work for our <em>Conn<\/em> objects:<\/p>\n<p><img alt='' class='alignnone size-full wp-image-448 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e93ed4ec11a.png' \/><\/p>\n<p><img alt='' class='alignnone size-full wp-image-449 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e93f2a3afe3.png' \/><\/p>\n<p>Very little time is wasted with internal work or synchronization.<\/p>\n<h4>Cache locality<\/h4>\n<p>Another <strong>possible<\/strong> small benefit with this scenario is better CPU cache utilization. Worker threads will tend to execute a few handlers for a given <em>Conn<\/em> object before grabbing another <em>Conn<\/em> object.<\/p>\n<p><strong>Zoomed out, without strands<\/strong><\/p>\n<p><img alt='' class='alignnone size-full wp-image-451 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e945ead5b05.png' \/><\/p>\n<p><strong>Zoomed out, with strands<\/strong><\/p>\n<p><img alt='' class='alignnone size-full wp-image-450 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e945891d37a.png' \/><\/p>\n<p>I suspect in practice you will not end up with handlers biased like that, but since it&#8217;s a freebie that required no extra work, it&#8217;s welcome.<\/p>\n<h4>Strand implementation<\/h4>\n<p>As a exercise, I coded my own strand implementation. Probably not production ready, but I&#8217;d say it&#8217;s good enough to experiment with.<\/p>\n<p>First, lets think about what a strand should and should not guarantee, and what that means for the implementation:<\/p>\n<ol>\n<li>No handlers can execute concurrently.\n<ul>\n<li>This requires us to detect if any (and what) worker thread is currently running the strand. If the strand is in this state, we say it is <em>Running<\/em> .<\/li>\n<li>To avoid blocking, this means the strand needs to have a handler queue, so it can enqueue handlers for later execution if it is running in another thread.<\/li>\n<\/ul>\n<\/li>\n<li>Handlers are only executed from worker threads (In Boost Asio&#8217;s terms, a thread running <em>io_service::run<\/em>)\n<ul>\n<li>This also implies the use of the strand&#8217;s handler queue, since adding handlers to the strand from a thread which is not a worker thread will require the handler to be enqueued.<\/li>\n<\/ul>\n<\/li>\n<li>Handler execution order is not guaranteed\n<ul>\n<li>Since we want to be able to add handlers to the strand from several threads, we cannot guarantee the handler execution order.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>The bulk of the implementation is centered around 3 methods we need for the strand interface (similar to Boost Asio&#8217;s strands )<\/p>\n<ul>\n<li><em>post<\/em>\n<ul>\n<li>Adds a handler to be executed at a later time. It never executes the handler as a result of the call.<\/li>\n<\/ul>\n<\/li>\n<li><em>dispatch<\/em>\n<ul>\n<li>Executes the handler right-away if all the guarantees are met, or calls <em>post<\/em> if not.<\/li>\n<\/ul>\n<\/li>\n<li><em>run<\/em>\n<ul>\n<li>Executes any pending handlers. This is not part of the public interface.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Putting aside synchronization for now, we can draft the behavior of those 3 methods. Overall, it&#8217;s quite straightforward.<\/p>\n<p><img alt='' class='alignnone size-full wp-image-460 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e9f3ade99a5.png' \/><\/p>\n<p><img alt='' class='alignnone size-full wp-image-461 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e9f3e58ba53.png' \/><\/p>\n<p><img alt='' class='alignnone size-full wp-image-462 ' src='https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56e9f4151d97c.png' \/><\/p>\n<p>For the actual source code, we need two helper classes I introduced earlier:<\/p>\n<ul>\n<li><a href=\"http:\/\/www.crazygaze.com\/blog\/2016\/03\/11\/callstack-markers-boostasiodetailcall_stack\/\">Callstack<\/a>\n<ul>\n<li>Allows placing markers in the current callstack, to detect if we are executing within a given function\/method in the current thread.<\/li>\n<\/ul>\n<\/li>\n<li><a href=\"http:\/\/www.crazygaze.com\/blog\/2016\/03\/13\/simple-way-to-shutdown-multiple-consumer-threads\/\">WorkQueue<\/a>\n<ul>\n<li>Simple multiple producer\/multiple consumer work queue. The consumers block waiting for work.<\/li>\n<li>Useful as-is, but introduced mostly to simplify the sample code, since in practice you will probably be using strands with something more complex.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>There is also <code>Monitor&lt;T&gt;<\/code> , which allows you to enforce synchronization when accessing a given object of type <em>T<\/em>. You can learn more about it at <a href=\"\">https:\/\/channel9.msdn.com\/Shows\/Going+Deep\/C-and-Beyond-2012-Herb-Sutter-Concurrency-and-Parallelism<\/a> at around minute 39 , but here it is the implementation I&#8217;ll be using:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\ntemplate &lt;class T&gt;\r\nclass Monitor {\r\nprivate:\r\n    mutable T m_t;\r\n    mutable std::mutex m_mtx;\r\n\r\npublic:\r\n    using Type = T;\r\n    Monitor() {}\r\n    Monitor(T t_) : m_t(std::move(t_)) {}\r\n    template &lt;typename F&gt;\r\n    auto operator()(F f) const -&gt; decltype(f(m_t)) {\r\n        std::lock_guard&lt;std::mutex&gt; hold{m_mtx};\r\n        return f(m_t);\r\n    }\r\n};\r\n<\/pre>\n<p>The strand implementation is pretty much what you see in the diagrams above, but with the required internal synchronization. Heavily documented to explain what&#8217;s going on.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#pragma once\r\n#include &quot;Callstack.h&quot;\r\n#include &quot;Monitor.h&quot;\r\n#include &lt;assert.h&gt;\r\n#include &lt;queue&gt;\r\n#include &lt;functional&gt;\r\n\r\n\/\/\r\n\/\/ A strand serializes handler execution.\r\n\/\/ It guarantees the following:\r\n\/\/ - No handlers executes concurrently\r\n\/\/ - Handlers are only executed from the specified Processor\r\n\/\/ - Handler execution order is not guaranteed\r\n\/\/\r\n\/\/ Specified Processor must implement the following interface:\r\n\/\/\r\n\/\/\ttemplate &lt;typename F&gt; void Processor::push(F w);\r\n\/\/\t\tAdd a new work item to the processor. F is a callable convertible\r\n\/\/ to std::function&lt;void()&gt;\r\n\/\/\r\n\/\/\tbool Processor::canDispatch();\r\n\/\/\t\tShould return true if we are in the Processor's dispatching function in\r\n\/\/ the current thread.\r\n\/\/\r\ntemplate &lt;typename Processor&gt;\r\nclass Strand {\r\npublic:\r\n    Strand(Processor&amp; proc) : m_proc(proc) {}\r\n\r\n    Strand(const Strand&amp;) = delete;\r\n    Strand&amp; operator=(const Strand&amp;) = delete;\r\n\r\n    \/\/ Executes the handler immediately if all the strand guarantees are met,\r\n    \/\/ or posts the handler for later execution if the guarantees are not met\r\n    \/\/ from inside this call\r\n    template &lt;typename F&gt;\r\n    void dispatch(F handler) {\r\n        \/\/ If we are not currently in the processor dispatching function (in\r\n        \/\/ this thread), then we cannot possibly execute the handler here, so\r\n        \/\/ enqueue it and bail out\r\n        if (!m_proc.canDispatch()) {\r\n            post(std::move(handler));\r\n            return;\r\n        }\r\n\r\n        \/\/ NOTE: At this point we know we are in a worker thread (because of the\r\n        \/\/ check above)\r\n\r\n        \/\/ If we are running the strand in this thread, then we can execute the\r\n        \/\/ handler immediately without any other checks, since by design no\r\n        \/\/ other threads can be running the strand\r\n        if (runningInThisThread()) {\r\n            handler();\r\n            return;\r\n        }\r\n\r\n        \/\/ At this point we know we are in a worker thread, but not running the\r\n        \/\/ strand in this thread.\r\n        \/\/ The strand can still be running in another worker thread, so we need\r\n        \/\/ to atomically enqueue the handler for the other thread to execute OR\r\n        \/\/ mark the strand as running in this thread\r\n        auto trigger = m_data([&amp;](Data&amp; data) {\r\n            if (data.running) {\r\n                data.q.push(std::move(handler));\r\n                return false;\r\n            } else {\r\n                data.running = true;\r\n                return true;\r\n            }\r\n        });\r\n\r\n        if (trigger) {\r\n            \/\/ Add a marker to the callstack, so the handler knows the strand is\r\n            \/\/ running in the current thread\r\n            Callstack&lt;Strand&gt;::Context ctx(this);\r\n            handler();\r\n\r\n            \/\/ Run any remaining handlers.\r\n            \/\/ At this point we own the strand (It's marked as running in\r\n            \/\/ this thread), and we don't release it until the queue is empty.\r\n            \/\/ This means any other threads adding handlers to the strand will\r\n            \/\/ enqueue them, and they will be executed here.\r\n            run();\r\n        }\r\n    }\r\n\r\n    \/\/ Post an handler for execution and returns immediately.\r\n    \/\/ The handler is never executed as part of this call.\r\n    template &lt;typename F&gt;\r\n    void post(F handler) {\r\n        \/\/ We atomically enqueue the handler AND check if we need to start the\r\n        \/\/ running process.\r\n        bool trigger = m_data([&amp;](Data&amp; data) {\r\n            data.q.push(std::move(handler));\r\n            if (data.running) {\r\n                return false;\r\n            } else {\r\n                data.running = true;\r\n                return true;\r\n            }\r\n        });\r\n\r\n        \/\/ The strand was not running, so trigger a run\r\n        if (trigger) {\r\n            m_proc.push([this] { run(); });\r\n        }\r\n    }\r\n\r\n    \/\/ Checks if we are currently running the strand in this thread\r\n    bool runningInThisThread() {\r\n        return Callstack&lt;Strand&gt;::contains(this) != nullptr;\r\n    }\r\n\r\nprivate:\r\n    \/\/ Processes any enqueued handlers.\r\n    \/\/ This assumes the strand is marked as running.\r\n    \/\/ When there are no more handlers, it marks the strand as not running.\r\n    void run() {\r\n        Callstack&lt;Strand&gt;::Context ctx(this);\r\n        while (true) {\r\n            std::function&lt;void()&gt; handler;\r\n            m_data([&amp;](Data&amp; data) {\r\n                assert(data.running);\r\n                if (data.q.size()) {\r\n                    handler = std::move(data.q.front());\r\n                    data.q.pop();\r\n                } else {\r\n                    data.running = false;\r\n                }\r\n            });\r\n\r\n            if (handler)\r\n                handler();\r\n            else\r\n                return;\r\n        }\r\n    }\r\n\r\n    struct Data {\r\n        bool running = false;\r\n        std::queue&lt;std::function&lt;void()&gt;&gt; q;\r\n    };\r\n    Monitor&lt;Data&gt; m_data;\r\n    Processor&amp; m_proc;\r\n};\r\n<\/pre>\n<p>Note that <em>Strand<\/em> is templated. You need to specify a suitable <em>Processor<\/em> type. This was mostly to allow me to share the code as-is, without more dependencies.<\/p>\n<p>Another peculiar thing to note is that the strand doesn&#8217;t use the <em>Processor<\/em> to execute handlers, as mentioned in the beginning. It uses it to trigger execution of its own <em>run<\/em> method.<\/p>\n<h4>Usage example<\/h4>\n<p>A simple useless sample demonstrating how it can be used.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &quot;Strand.h&quot;\r\n#include &quot;WorkQueue.h&quot;\r\n#include &lt;random&gt;\r\n#include &lt;stdlib.h&gt;\r\n#include &lt;string&gt;\r\n#include &lt;atomic&gt;\r\n\r\n\/\/ http:\/\/stackoverflow.com\/questions\/7560114\/random-number-c-in-some-range\r\nint randInRange(int min, int max) {\r\n    std::random_device rd;   \/\/ obtain a random number from hardware\r\n    std::mt19937 eng(rd());  \/\/ seed the generator\r\n    std::uniform_int_distribution&lt;&gt; distr(min, max);  \/\/ define the range\r\n    return distr(eng);\r\n}\r\n\r\nstruct Obj {\r\n    explicit Obj(int n, WorkQueue&amp; wp) : strand(wp) {\r\n        name = &quot;Obj &quot; + std::to_string(n);\r\n    }\r\n\r\n    void doSomething(int val) {\r\n        printf(&quot;%s : doing %dn&quot;, name.c_str(), val);\r\n    }\r\n    std::string name;\r\n    Strand&lt;WorkQueue&gt; strand;\r\n};\r\n\r\nvoid strandSample() {\r\n    WorkQueue workQueue;\r\n    \/\/ Start a couple of worker threads\r\n    std::vector&lt;std::thread&gt; workerThreads;\r\n    for (int i = 0; i &lt; 4; i++) {\r\n        workerThreads.push_back(std::thread([&amp;workQueue] { workQueue.run(); }));\r\n    }\r\n\r\n    \/\/ Create a couple of objects that need strands\r\n    std::vector&lt;std::unique_ptr&lt;Obj&gt;&gt; objs;\r\n    for (int i = 0; i &lt; 8; i++) {\r\n        objs.push_back(std::make_unique&lt;Obj&gt;(i, workQueue));\r\n    }\r\n\r\n    \/\/ Counter used by all strands, so we can check if all work was done\r\n    std::atomic&lt;int&gt; doneCount(0);\r\n\r\n    \/\/ Add work to random objects\r\n    const int todo = 20;\r\n    for (int i = 0; i &lt; todo; i++) {\r\n        auto&amp;&amp; obj = objs[randInRange(0, objs.size() - 1)];\r\n        obj-&gt;strand.post([&amp;obj, i, &amp;doneCount] {\r\n            obj-&gt;doSomething(i);\r\n            ++doneCount;\r\n        });\r\n    }\r\n\r\n    workQueue.stop();\r\n    for (auto&amp;&amp; t : workerThreads) {\r\n        t.join();\r\n    }\r\n\r\n    assert(doneCount == todo);\r\n}\r\n<\/pre>\n<p>And the output&#8230;<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nObj 2 : doing 0\r\nObj 1 : doing 1\r\nObj 1 : doing 3\r\nObj 1 : doing 4\r\nObj 3 : doing 6\r\nObj 5 : doing 2\r\nObj 4 : doing 5\r\nObj 6 : doing 11\r\nObj 3 : doing 8\r\nObj 5 : doing 10\r\nObj 5 : doing 12\r\nObj 6 : doing 17\r\nObj 3 : doing 9\r\nObj 3 : doing 13\r\nObj 5 : doing 18\r\nObj 0 : doing 14\r\nObj 2 : doing 15\r\nObj 3 : doing 16\r\nObj 5 : doing 19\r\nObj 1 : doing 7\r\n<\/pre>\n<h4>Conclusion<\/h4>\n<ul>\n<li>No explicit synchronization required for handlers going through a given strand.\n<ul>\n<li>Big plus. Greatly simplifies your code, so less bugs.<\/li>\n<\/ul>\n<\/li>\n<li>Less blocking overall\n<ul>\n<li>Big or small plus depending on your connections\/threads ratio. The more connections you have, the less likely two worker threads will do work on a connection concurrently, and thus less blocking.<\/li>\n<\/ul>\n<\/li>\n<li>Cache locality\n<ul>\n<li>I&#8217;d say small plus. Highly dependent on your scenario. But it&#8217;s a freebie that comes with no extra complexity, so I&#8217;ll take it.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>I don&#8217;t see any obvious downside of using explicit strands, unless you stick with just one worker thread (which is an implicit strand on its own).<\/p>\n<p>Feel free to comment with corrections or questions.<\/p>\n<h3>License<\/h3>\n<p>The source code in this article is licensed under the <a href=\"https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/\">CC0 license<\/a>, so feel free to copy, modify, share, do whatever you want with it.<br \/>\nNo attribution is required, but I&#8217;ll be happy if you do.<\/p>\n<h6>Update 25\/03\/2016<\/h6>\n<ul>\n<li><a href=\"https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/Strand_Sandbox.zip\"rel=\"\">Strand_Sandbox.zip<\/a>\n<ul>\n<li>This is the Visual Studio 2015 project I used while writing this post. It contains the code used to benchmark the mentioned scenario.<\/li>\n<li>The version of Remotery included with the project has some custom changes for assigning colors to blocks by name. This is <em>NOT<\/em> production quality code. It&#8217;s a hack and not recommended.<\/li>\n<li>As the name suggests, its a sandbox project. Code is not production quality.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>If you ever used Boost Asio, certainly you used or at least looked at strands . The main benefit of using strands is to simplify your code, since handlers that go through a strand don&#8217;t need explicit synchronization. A strand guarantees that no two handlers execute concurrently. If you use just one IO thread (or [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":468,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_mi_skip_tracking":false,"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[50],"tags":[14,12,52,10,15],"jetpack_featured_media_url":"https:\/\/www.crazygaze.com\/blog\/wp-content\/uploads\/2016\/03\/img_56eab6e68d66d.png","jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p7jpe0-6X","_links":{"self":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts\/431"}],"collection":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/comments?post=431"}],"version-history":[{"count":26,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts\/431\/revisions"}],"predecessor-version":[{"id":739,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/posts\/431\/revisions\/739"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/media\/468"}],"wp:attachment":[{"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/media?parent=431"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/categories?post=431"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.crazygaze.com\/blog\/wp-json\/wp\/v2\/tags?post=431"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}